diff --git a/GITALY_SERVER_VERSION b/GITALY_SERVER_VERSION
index d9df1bbc0c7befdbc28d61efc28ed3e5c08d015f..bc859cbd6d998ecfd13ee4c49cd5da054afb36ad 100644
--- a/GITALY_SERVER_VERSION
+++ b/GITALY_SERVER_VERSION
@@ -1 +1 @@
-0.11.0
+0.11.2
diff --git a/GITLAB_WORKHORSE_VERSION b/GITLAB_WORKHORSE_VERSION
index 227cea215648b1af34a87c9acf5b707fe02d2072..3e3c2f1e5edb083aab93646ac7b076daa38516dd 100644
--- a/GITLAB_WORKHORSE_VERSION
+++ b/GITLAB_WORKHORSE_VERSION
@@ -1 +1 @@
-2.0.0
+2.1.1
diff --git a/Gemfile b/Gemfile
index e197f53d9b57485622341709acfd726ffac0c46c..c78c1f2fc4c2fddaf96d8a3daff9de817541544b 100644
--- a/Gemfile
+++ b/Gemfile
@@ -2,6 +2,7 @@ source 'https://rubygems.org'
 
 gem 'rails', '4.2.8'
 gem 'rails-deprecated_sanitizer', '~> 1.0.3'
+gem 'bootsnap', '~> 1.0.0'
 
 # Responders respond_to and respond_with
 gem 'responders', '~> 2.0'
@@ -263,6 +264,17 @@ gem 'gettext_i18n_rails', '~> 1.8.0'
 gem 'gettext_i18n_rails_js', '~> 1.2.0'
 gem 'gettext', '~> 3.2.2', require: false, group: :development
 
+# Perf bar
+gem 'peek', '~> 1.0.1'
+gem 'peek-gc', '~> 0.0.2'
+gem 'peek-host', '~> 1.0.0'
+gem 'peek-mysql2', '~> 1.1.0', group: :mysql
+gem 'peek-performance_bar', '~> 1.2.1'
+gem 'peek-pg', '~> 1.3.0', group: :postgres
+gem 'peek-rblineprof', '~> 0.2.0'
+gem 'peek-redis', '~> 1.2.0'
+gem 'peek-sidekiq', '~> 1.0.3'
+
 # Metrics
 group :metrics do
   gem 'allocations', '~> 1.0', require: false, platform: :mri
diff --git a/Gemfile.lock b/Gemfile.lock
index b5f9c3beca75f1f70307e05d1afc1be99621a6b8..676cd977e37ff34e7443df0d0b7880df46a78491 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -56,6 +56,7 @@ GEM
     asciidoctor-plantuml (0.0.7)
       asciidoctor (~> 1.5)
     ast (2.3.0)
+    atomic (1.1.99)
     attr_encrypted (3.0.3)
       encryptor (~> 3.0.0)
     attr_required (1.0.0)
@@ -82,6 +83,8 @@ GEM
     bindata (2.3.5)
     binding_of_caller (0.7.2)
       debug_inspector (>= 0.0.1)
+    bootsnap (1.0.0)
+      msgpack (~> 1.0)
     bootstrap-sass (3.3.6)
       autoprefixer-rails (>= 5.2.1)
       sass (>= 3.3.4)
@@ -129,6 +132,8 @@ GEM
     coffee-script-source (1.10.0)
     colorize (0.7.7)
     concurrent-ruby (1.0.5)
+    concurrent-ruby-ext (1.0.5)
+      concurrent-ruby (= 1.0.5)
     connection_pool (2.2.1)
     crack (0.4.3)
       safe_yaml (~> 1.0.0)
@@ -459,6 +464,7 @@ GEM
     minitest (5.7.0)
     mmap2 (2.2.6)
     mousetrap-rails (1.4.6)
+    msgpack (1.1.0)
     multi_json (1.12.1)
     multi_xml (0.6.0)
     multipart-post (2.0.0)
@@ -545,6 +551,36 @@ GEM
     parser (2.4.0.0)
       ast (~> 2.2)
     path_expander (1.0.1)
+    peek (1.0.1)
+      concurrent-ruby (>= 0.9.0)
+      concurrent-ruby-ext (>= 0.9.0)
+      railties (>= 4.0.0)
+    peek-gc (0.0.2)
+      peek
+    peek-host (1.0.0)
+      peek
+    peek-mysql2 (1.1.0)
+      atomic (>= 1.0.0)
+      mysql2
+      peek
+    peek-performance_bar (1.2.1)
+      peek (>= 0.1.0)
+    peek-pg (1.3.0)
+      concurrent-ruby
+      concurrent-ruby-ext
+      peek
+      pg
+    peek-rblineprof (0.2.0)
+      peek
+      rblineprof
+    peek-redis (1.2.0)
+      atomic (>= 1.0.0)
+      peek
+      redis
+    peek-sidekiq (1.0.3)
+      atomic (>= 1.0.0)
+      peek
+      sidekiq
     pg (0.18.4)
     po_to_json (1.0.1)
       json (>= 1.6.0)
@@ -888,6 +924,7 @@ DEPENDENCIES
   benchmark-ips (~> 2.3.0)
   better_errors (~> 2.1.0)
   binding_of_caller (~> 0.7.2)
+  bootsnap (~> 1.0.0)
   bootstrap-sass (~> 3.3.0)
   brakeman (~> 3.6.0)
   browser (~> 2.2)
@@ -995,6 +1032,15 @@ DEPENDENCIES
   omniauth_crowd (~> 2.2.0)
   org-ruby (~> 0.9.12)
   paranoia (~> 2.2)
+  peek (~> 1.0.1)
+  peek-gc (~> 0.0.2)
+  peek-host (~> 1.0.0)
+  peek-mysql2 (~> 1.1.0)
+  peek-performance_bar (~> 1.2.1)
+  peek-pg (~> 1.3.0)
+  peek-rblineprof (~> 0.2.0)
+  peek-redis (~> 1.2.0)
+  peek-sidekiq (~> 1.0.3)
   pg (~> 0.18.2)
   poltergeist (~> 1.9.0)
   premailer-rails (~> 1.9.0)
diff --git a/VERSION b/VERSION
index d821c124047fbe5b43f55d51158e96205f9f5c48..99a1afa4e58c0af427bb81472f59f5101c2d92ac 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-9.3.0-pre
+9.3.0-rc2
diff --git a/app/assets/javascripts/blob/blob_file_dropzone.js b/app/assets/javascripts/blob/blob_file_dropzone.js
index 4568b86f2980be218239addc87a7b07f0b7a2ee5..dc636050221bc8ae1916e286e791c081e4967a71 100644
--- a/app/assets/javascripts/blob/blob_file_dropzone.js
+++ b/app/assets/javascripts/blob/blob_file_dropzone.js
@@ -35,7 +35,7 @@ export default class BlobFileDropzone {
           this.removeFile(file);
         });
         this.on('sending', function (file, xhr, formData) {
-          formData.append('branch_name', form.find('input[name="branch_name"]').val());
+          formData.append('branch_name', form.find('.js-branch-name').val());
           formData.append('create_merge_request', form.find('.js-create-merge-request').val());
           formData.append('commit_message', form.find('.js-commit-message').val());
         });
diff --git a/app/assets/javascripts/blob/create_branch_dropdown.js b/app/assets/javascripts/blob/create_branch_dropdown.js
deleted file mode 100644
index 95517f51b1c76ae3f224da94e6d89649d9eae425..0000000000000000000000000000000000000000
--- a/app/assets/javascripts/blob/create_branch_dropdown.js
+++ /dev/null
@@ -1,88 +0,0 @@
-class CreateBranchDropdown {
-  constructor(el, targetBranchDropdown) {
-    this.targetBranchDropdown = targetBranchDropdown;
-    this.el = el;
-    this.dropdownBack = this.el.closest('.dropdown').querySelector('.dropdown-menu-back');
-    this.cancelButton = this.el.querySelector('.js-cancel-branch-btn');
-    this.newBranchField = this.el.querySelector('#new_branch_name');
-    this.newBranchCreateButton = this.el.querySelector('.js-new-branch-btn');
-
-    this.newBranchCreateButton.setAttribute('disabled', '');
-
-    this.addBindings();
-    this.cleanupWrapper = this.cleanup.bind(this);
-    document.addEventListener('beforeunload', this.cleanupWrapper);
-  }
-
-  cleanup() {
-    this.cleanBindings();
-    document.removeEventListener('beforeunload', this.cleanupWrapper);
-  }
-
-  cleanBindings() {
-    this.newBranchField.removeEventListener('keyup', this.enableBranchCreateButtonWrapper);
-    this.newBranchField.removeEventListener('change', this.enableBranchCreateButtonWrapper);
-    this.newBranchField.removeEventListener('keydown', this.handleNewBranchKeydownWrapper);
-    this.dropdownBack.removeEventListener('click', this.resetFormWrapper);
-    this.cancelButton.removeEventListener('click', this.handleCancelClickWrapper);
-    this.newBranchCreateButton.removeEventListener('click', this.createBranchWrapper);
-  }
-
-  addBindings() {
-    this.enableBranchCreateButtonWrapper = this.enableBranchCreateButton.bind(this);
-    this.handleNewBranchKeydownWrapper = this.handleNewBranchKeydown.bind(this);
-    this.resetFormWrapper = this.resetForm.bind(this);
-    this.handleCancelClickWrapper = this.handleCancelClick.bind(this);
-    this.createBranchWrapper = this.createBranch.bind(this);
-
-    this.newBranchField.addEventListener('keyup', this.enableBranchCreateButtonWrapper);
-    this.newBranchField.addEventListener('change', this.enableBranchCreateButtonWrapper);
-    this.newBranchField.addEventListener('keydown', this.handleNewBranchKeydownWrapper);
-    this.dropdownBack.addEventListener('click', this.resetFormWrapper);
-    this.cancelButton.addEventListener('click', this.handleCancelClickWrapper);
-    this.newBranchCreateButton.addEventListener('click', this.createBranchWrapper);
-  }
-
-  handleCancelClick(e) {
-    e.preventDefault();
-    e.stopPropagation();
-
-    this.resetForm();
-    this.dropdownBack.click();
-  }
-
-  handleNewBranchKeydown(e) {
-    const keyCode = e.which;
-    const ENTER_KEYCODE = 13;
-    if (keyCode === ENTER_KEYCODE) {
-      this.createBranch(e);
-    }
-  }
-
-  enableBranchCreateButton() {
-    if (this.newBranchField.value !== '') {
-      this.newBranchCreateButton.removeAttribute('disabled');
-    } else {
-      this.newBranchCreateButton.setAttribute('disabled', '');
-    }
-  }
-
-  resetForm() {
-    this.newBranchField.value = '';
-    this.enableBranchCreateButtonWrapper();
-  }
-
-  createBranch(e) {
-    e.preventDefault();
-
-    if (this.newBranchCreateButton.getAttribute('disabled') === '') {
-      return;
-    }
-    const newBranchName = this.newBranchField.value;
-    this.targetBranchDropdown.setNewBranch(newBranchName);
-    this.resetForm();
-  }
-}
-
-window.gl = window.gl || {};
-gl.CreateBranchDropdown = CreateBranchDropdown;
diff --git a/app/assets/javascripts/blob/target_branch_dropdown.js b/app/assets/javascripts/blob/target_branch_dropdown.js
deleted file mode 100644
index d52d69b1274888586b6c89791aabe150d1d06366..0000000000000000000000000000000000000000
--- a/app/assets/javascripts/blob/target_branch_dropdown.js
+++ /dev/null
@@ -1,152 +0,0 @@
-/* eslint-disable class-methods-use-this */
-const SELECT_ITEM_MSG = 'Select';
-
-class TargetBranchDropDown {
-  constructor(dropdown) {
-    this.dropdown = dropdown;
-    this.$dropdown = $(dropdown);
-    this.fieldName = this.dropdown.getAttribute('data-field-name');
-    this.form = this.dropdown.closest('form');
-    this.createDropdown();
-  }
-
-  static bootstrap() {
-    const dropdowns = document.querySelectorAll('.js-project-branches-dropdown');
-    [].forEach.call(dropdowns, dropdown => new TargetBranchDropDown(dropdown));
-  }
-
-  createDropdown() {
-    const self = this;
-    this.$dropdown.glDropdown({
-      selectable: true,
-      filterable: true,
-      search: {
-        fields: ['title'],
-      },
-      data: (term, callback) => $.ajax({
-        url: self.dropdown.getAttribute('data-refs-url'),
-        data: {
-          ref: self.dropdown.getAttribute('data-ref'),
-          show_all: true,
-        },
-        dataType: 'json',
-      }).done(refs => callback(self.dropdownData(refs))),
-      toggleLabel(item, el) {
-        if (el.is('.is-active')) {
-          return item.text;
-        }
-        return SELECT_ITEM_MSG;
-      },
-      clicked(options) {
-        options.e.preventDefault();
-        self.onClick.call(self);
-      },
-      fieldName: self.fieldName,
-    });
-    return new gl.CreateBranchDropdown(this.form.querySelector('.dropdown-new-branch'), this);
-  }
-
-  onClick() {
-    this.enableSubmit();
-    this.$dropdown.trigger('change.branch');
-  }
-
-  enableSubmit() {
-    const submitBtn = this.form.querySelector('[type="submit"]');
-    if (this.branchInput && this.branchInput.value) {
-      submitBtn.removeAttribute('disabled');
-    } else {
-      submitBtn.setAttribute('disabled', '');
-    }
-  }
-
-  dropdownData(refs) {
-    const branchList = this.dropdownItems(refs);
-    this.cachedRefs = refs;
-    this.addDefaultBranch(branchList);
-    this.addNewBranch(branchList);
-    return { Branches: branchList };
-  }
-
-  dropdownItems(refs) {
-    return refs.map(this.dropdownItem);
-  }
-
-  dropdownItem(ref) {
-    return { id: ref, text: ref, title: ref };
-  }
-
-  addDefaultBranch(branchList) {
-    // when no branch is selected do nothing
-    if (!this.branchInput) {
-      return;
-    }
-
-    const branchInputVal = this.branchInput.value;
-    const currentBranchIndex = this.searchBranch(branchList, branchInputVal);
-
-    if (currentBranchIndex === -1) {
-      this.unshiftBranch(branchList, this.dropdownItem(branchInputVal));
-    }
-  }
-
-  addNewBranch(branchList) {
-    if (this.newBranch) {
-      this.unshiftBranch(branchList, this.newBranch);
-    }
-  }
-
-  searchBranch(branchList, branchName) {
-    return _.findIndex(branchList, el => branchName === el.id);
-  }
-
-  unshiftBranch(branchList, branch) {
-    const branchIndex = this.searchBranch(branchList, branch.id);
-
-    if (branchIndex === -1) {
-      branchList.unshift(branch);
-    }
-  }
-
-  setNewBranch(newBranchName) {
-    this.newBranch = this.dropdownItem(newBranchName);
-    this.refreshData();
-    this.selectBranch(this.searchBranch(this.glDropdown.fullData.Branches, newBranchName));
-  }
-
-  refreshData() {
-    this.glDropdown.fullData = this.dropdownData(this.cachedRefs);
-    this.clearFilter();
-  }
-
-  clearFilter() {
-    // apply an empty filter in order to refresh the data
-    this.glDropdown.filter.filter('');
-    this.dropdown.closest('.dropdown').querySelector('.dropdown-page-one .dropdown-input-field').value = '';
-  }
-
-  selectBranch(index) {
-    const branch = this.dropdown.closest('.dropdown').querySelectorAll('li a')[index];
-
-    if (!branch.classList.contains('is-active')) {
-      branch.click();
-    } else {
-      this.closeDropdown();
-    }
-  }
-
-  closeDropdown() {
-    this.dropdown.closest('.dropdown').querySelector('.dropdown-menu-close').click();
-  }
-
-  get branchInput() {
-    return this.form.querySelector(`input[name="${this.fieldName}"]`);
-  }
-
-  get glDropdown() {
-    return this.$dropdown.data('glDropdown');
-  }
-}
-
-window.gl = window.gl || {};
-gl.TargetBranchDropDown = TargetBranchDropDown;
diff --git a/app/assets/javascripts/build.js b/app/assets/javascripts/build.js
index 3b2bb6f082f79ec2cbbfe235a42adff05e94a7ac..d80b7f5bd4218748d4b327958e36035ce875032a 100644
--- a/app/assets/javascripts/build.js
+++ b/app/assets/javascripts/build.js
@@ -20,6 +20,7 @@ window.Build = (function () {
     this.$document = $(document);
     this.logBytes = 0;
     this.scrollOffsetPadding = 30;
+    this.hasBeenScrolled = false;
 
     this.updateDropdown = this.updateDropdown.bind(this);
     this.getBuildTrace = this.getBuildTrace.bind(this);
@@ -62,6 +63,15 @@ window.Build = (function () {
       .off('click')
       .on('click', this.scrollToBottom.bind(this));
 
+    const scrollThrottled = _.throttle(this.toggleScroll.bind(this), 100);
+
+    this.$scrollContainer
+      .off('scroll')
+      .on('scroll', () => {
+        this.hasBeenScrolled = true;
+        scrollThrottled();
+      });
+
     $(window)
       .off('resize.build')
       .on('resize.build', _.throttle(this.sidebarOnResize.bind(this), 100));
@@ -70,25 +80,16 @@ window.Build = (function () {
 
     // eslint-disable-next-line
     this.getBuildTrace()
-      .then(() => this.makeTraceScrollable())
-      .then(() => this.scrollToBottom());
+      .then(() => this.toggleScroll())
+      .then(() => {
+        if (!this.hasBeenScrolled) {
+          this.scrollToBottom();
+        }
+      });
 
     this.verifyTopPosition();
   }
 
-  Build.prototype.makeTraceScrollable = function () {
-    this.$scrollContainer.niceScroll({
-      cursorcolor: '#fff',
-      cursoropacitymin: 1,
-      cursorwidth: '7px',
-      railpadding: { top: 5, bottom: 5, right: 5 },
-    });
-
-    this.$scrollContainer.on('scroll', _.throttle(this.toggleScroll.bind(this), 100));
-
-    this.toggleScroll();
-  };
-
   Build.prototype.canScroll = function () {
     return (this.$scrollContainer.prop('scrollHeight') - this.scrollOffsetPadding) > this.$scrollContainer.height();
   };
@@ -104,12 +105,11 @@ window.Build = (function () {
    *
    */
   Build.prototype.toggleScroll = function () {
-    const bottomScroll = this.$scrollContainer.scrollTop() +
-      this.scrollOffsetPadding +
-      this.$scrollContainer.height();
+    const currentPosition = this.$scrollContainer.scrollTop();
+    const bottomScroll = currentPosition + this.$scrollContainer.innerHeight();
 
     if (this.canScroll()) {
-      if (this.$scrollContainer.scrollTop() === 0) {
+      if (currentPosition === 0) {
         this.toggleDisableButton(this.$scrollTopBtn, true);
         this.toggleDisableButton(this.$scrollBottomBtn, false);
       } else if (bottomScroll === this.$scrollContainer.prop('scrollHeight')) {
@@ -123,12 +123,14 @@ window.Build = (function () {
   };
 
   Build.prototype.scrollToTop = function () {
-    this.$scrollContainer.getNiceScroll(0).doScrollTop(0);
+    this.hasBeenScrolled = true;
+    this.$scrollContainer.scrollTop(0);
     this.toggleScroll();
   };
 
   Build.prototype.scrollToBottom = function () {
-    this.$scrollContainer.getNiceScroll(0).doScrollTo(this.$scrollContainer.prop('scrollHeight'));
+    this.hasBeenScrolled = true;
+    this.$scrollContainer.scrollTop(this.$scrollContainer.prop('scrollHeight'));
     this.toggleScroll();
   };
 
@@ -216,7 +218,11 @@ window.Build = (function () {
           Build.timeout = setTimeout(() => {
             //eslint-disable-next-line
             this.getBuildTrace()
-              .then(() => this.scrollToBottom());
+              .then(() => {
+                if (!this.hasBeenScrolled) {
+                  this.scrollToBottom();
+                }
+              });
           }, 4000);
         } else {
           this.$buildRefreshAnimation.remove();
@@ -253,7 +259,7 @@ window.Build = (function () {
 
     this.verifyTopPosition();
 
-    if (this.$scrollContainer.getNiceScroll(0)) {
+    if (this.canScroll()) {
       this.toggleScroll();
     }
   };
diff --git a/app/assets/javascripts/dispatcher.js b/app/assets/javascripts/dispatcher.js
index 51cc8c085b279172908806d588cc52dcd9f79b5d..ca90729c7916c6c325301e16a46e7492c8f3f28a 100644
--- a/app/assets/javascripts/dispatcher.js
+++ b/app/assets/javascripts/dispatcher.js
@@ -329,25 +329,14 @@ import initSettingsPanels from './settings_panels';
           shortcut_handler = new ShortcutsNavigation();
           new TreeView();
           new BlobViewer();
-          gl.TargetBranchDropDown.bootstrap();
           break;
         case 'projects:find_file:show':
           shortcut_handler = true;
           break;
-        case 'projects:blob:new':
-          gl.TargetBranchDropDown.bootstrap();
-          break;
-        case 'projects:blob:create':
-          gl.TargetBranchDropDown.bootstrap();
-          break;
         case 'projects:blob:show':
           new BlobViewer();
-          gl.TargetBranchDropDown.bootstrap();
           initBlob();
           break;
-        case 'projects:blob:edit':
-          gl.TargetBranchDropDown.bootstrap();
-          break;
         case 'projects:blame:show':
           initBlob();
           break;
diff --git a/app/assets/javascripts/main.js b/app/assets/javascripts/main.js
index fe367d0c42a57c2209f715f919ebbfa462ebcfdd..ed7629948ca2fce5e6c34bbecf35b9089b56abea 100644
--- a/app/assets/javascripts/main.js
+++ b/app/assets/javascripts/main.js
@@ -39,10 +39,6 @@ import './shortcuts_network';
 // behaviors
 import './behaviors/';
 
-// blob
-import './blob/create_branch_dropdown';
-import './blob/target_branch_dropdown';
-
 // templates
 import './templates/issuable_template_selector';
 import './templates/issuable_template_selectors';
diff --git a/app/assets/javascripts/new_commit_form.js b/app/assets/javascripts/new_commit_form.js
index 658879607e2aeeb836feca538ac54f95c24b5706..04073ef7270d891f6eb10b9819ef17bd032b7505 100644
--- a/app/assets/javascripts/new_commit_form.js
+++ b/app/assets/javascripts/new_commit_form.js
@@ -1,23 +1,20 @@
 /* eslint-disable func-names, space-before-function-paren, no-var, prefer-rest-params, wrap-iife, no-return-assign, max-len */
 (function() {
   this.NewCommitForm = (function() {
-    function NewCommitForm(form, targetBranchName = 'target_branch') {
+    function NewCommitForm(form) {
       this.form = form;
-      this.targetBranchName = targetBranchName;
       this.renderDestination = this.renderDestination.bind(this);
-      this.targetBranchDropdown = form.find('button.js-target-branch');
+      this.branchName = form.find('.js-branch-name');
       this.originalBranch = form.find('.js-original-branch');
       this.createMergeRequest = form.find('.js-create-merge-request');
       this.createMergeRequestContainer = form.find('.js-create-merge-request-container');
-      this.targetBranchDropdown.on('change.branch', this.renderDestination);
+      this.branchName.keyup(this.renderDestination);
       this.renderDestination();
     }
 
     NewCommitForm.prototype.renderDestination = function() {
       var different;
-      var targetBranch = this.form.find(`input[name="${this.targetBranchName}"]`);
-
-      different = targetBranch.val() !== this.originalBranch.val();
+      different = this.branchName.val() !== this.originalBranch.val();
       if (different) {
         this.createMergeRequestContainer.show();
         if (!this.wasDifferent) {
diff --git a/app/assets/javascripts/notes.js b/app/assets/javascripts/notes.js
index b0143b12cfefd568a2d1806960bda3ab293b0ee8..0a9cefd34c352114848a39e05d996714e16c9087 100644
--- a/app/assets/javascripts/notes.js
+++ b/app/assets/javascripts/notes.js
@@ -56,6 +56,7 @@ const normalizeNewlines = function(str) {
       this.toggleCommitList = this.toggleCommitList.bind(this);
       this.postComment = this.postComment.bind(this);
       this.clearFlashWrapper = this.clearFlash.bind(this);
+      this.onHashChange = this.onHashChange.bind(this);
 
       this.notes_url = notes_url;
       this.note_ids = note_ids;
@@ -127,7 +128,9 @@ const normalizeNewlines = function(str) {
       $(document).on('ajax:success', '.js-main-target-form', this.resetMainTargetForm);
       $(document).on('ajax:complete', '.js-main-target-form', this.reenableTargetFormSubmitButton);
       // when a key is clicked on the notes
-      return $(document).on('keydown', '.js-note-text', this.keydownNoteText);
+      $(document).on('keydown', '.js-note-text', this.keydownNoteText);
+      // When the URL fragment/hash has changed, `#note_xxx`
+      return $(window).on('hashchange', this.onHashChange);
     };
 
     Notes.prototype.cleanBinding = function() {
@@ -148,6 +151,7 @@ const normalizeNewlines = function(str) {
       $(document).off('ajax:success', '.js-main-target-form');
       $(document).off('ajax:success', '.js-discussion-note-form');
       $(document).off('ajax:complete', '.js-main-target-form');
+      $(window).off('hashchange', this.onHashChange);
     };
 
     Notes.initCommentTypeToggle = function (form) {
@@ -298,8 +302,27 @@ const normalizeNewlines = function(str) {
     Notes.prototype.setupNewNote = function($note) {
       // Update datetime format on the recent note
       gl.utils.localTimeAgo($note.find('.js-timeago'), false);
+
       this.collapseLongCommitList();
       this.taskList.init();
+
+      // This stops the note highlight, #note_xxx`, from being removed after real time update
+      // The `:target` selector does not re-evaluate after we replace element in the DOM
+      Notes.updateNoteTargetSelector($note);
+      this.$noteToCleanHighlight = $note;
+    };
+
+    Notes.prototype.onHashChange = function() {
+      if (this.$noteToCleanHighlight) {
+        Notes.updateNoteTargetSelector(this.$noteToCleanHighlight);
+      }
+
+      this.$noteToCleanHighlight = null;
+    };
+
+    Notes.updateNoteTargetSelector = function($note) {
+      const hash = gl.utils.getLocationHash();
+      $note.toggleClass('target', hash && $note.filter(`#${hash}`).length > 0);
     };
 
     /*
@@ -597,13 +620,12 @@ const normalizeNewlines = function(str) {
       $noteEntityEl = $(noteEntity.html);
       $noteEntityEl.addClass('fade-in-full');
       this.revertNoteEditForm($targetNote);
-      gl.utils.localTimeAgo($('.js-timeago', $noteEntityEl));
       $noteEntityEl.renderGFM();
-      $noteEntityEl.find('.js-task-list-container').taskList('enable');
       // Find the note's `li` element by ID and replace it with the updated HTML
       $note_li = $('.note-row-' + noteEntity.id);
 
       $note_li.replaceWith($noteEntityEl);
+      this.setupNewNote($noteEntityEl);
 
       if (typeof gl.diffNotesCompileComponents !== 'undefined') {
         gl.diffNotesCompileComponents();
@@ -1060,7 +1082,7 @@ const normalizeNewlines = function(str) {
       var targetId = $originalContentEl.data('target-id');
       var targetType = $originalContentEl.data('target-type');
 
-      new gl.GLForm($editForm.find('form'));
+      new gl.GLForm($editForm.find('form'), this.enableGFM);
 
       $editForm.find('form')
         .attr('action', postUrl)
@@ -1245,6 +1267,7 @@ const normalizeNewlines = function(str) {
      */
     Notes.prototype.createPlaceholderNote = function ({ formContent, uniqueId, isDiscussionNote, currentUsername, currentUserFullname, currentUserAvatar }) {
       const discussionClass = isDiscussionNote ? 'discussion' : '';
+      const escapedFormContent = _.escape(formContent);
       const $tempNote = $(
         `<li id="${uniqueId}" class="note being-posted fade-in-half timeline-entry">
            <div class="timeline-entry-inner">
@@ -1264,7 +1287,7 @@ const normalizeNewlines = function(str) {
                  </div>
                  <div class="note-body">
                    <div class="note-text">
-                     <p>${formContent}</p>
+                     <p>${escapedFormContent}</p>
                    </div>
                  </div>
               </div>
diff --git a/app/assets/javascripts/peek.js b/app/assets/javascripts/peek.js
new file mode 100644
index 0000000000000000000000000000000000000000..de1a99fa3bd04bdcc7a832f17d896139859f018a
--- /dev/null
+++ b/app/assets/javascripts/peek.js
@@ -0,0 +1,16 @@
+import 'vendor/peek';
+import 'vendor/peek.performance_bar';
+
+$(document).on('click', '#peek-show-queries', (e) => {
+  e.preventDefault();
+  $('.peek-rblineprof-modal').hide();
+  const $modal = $('#modal-peek-pg-queries');
+  if ($modal.length) {
+    $modal.modal('toggle');
+  }
+});
+
+$(document).on('click', '.js-lineprof-file', (e) => {
+  e.preventDefault();
+  $(e.target).parents('.peek-rblineprof-file').find('.data').toggle();
+});
diff --git a/app/assets/javascripts/shortcuts.js b/app/assets/javascripts/shortcuts.js
index 8ac71797c14c01cd478c9c339fe2b3927afeace4..a4a7f3fa9441f777e8f9d4667b3c57498e8682c1 100644
--- a/app/assets/javascripts/shortcuts.js
+++ b/app/assets/javascripts/shortcuts.js
@@ -1,6 +1,8 @@
 /* eslint-disable func-names, space-before-function-paren, no-var, prefer-rest-params, wrap-iife, quotes, prefer-arrow-callback, consistent-return, object-shorthand, no-unused-vars, one-var, one-var-declaration-per-line, no-else-return, comma-dangle, max-len */
 /* global Mousetrap */
 /* global findFileURL */
+import Cookies from 'js-cookie';
+
 import findAndFollowLink from './shortcuts_dashboard_navigation';
 
 (function() {
@@ -14,6 +16,7 @@ import findAndFollowLink from './shortcuts_dashboard_navigation';
       Mousetrap.bind('?', this.onToggleHelp);
       Mousetrap.bind('s', Shortcuts.focusSearch);
       Mousetrap.bind('f', (e => this.focusFilter(e)));
+      Mousetrap.bind('p b', this.onTogglePerfBar);
 
       const $globalDropdownMenu = $('.global-dropdown-menu');
       const $globalDropdownToggle = $('.global-dropdown-toggle');
@@ -53,6 +56,17 @@ import findAndFollowLink from './shortcuts_dashboard_navigation';
       return Shortcuts.toggleHelp(this.enabledHelp);
     };
 
+    Shortcuts.prototype.onTogglePerfBar = function(e) {
+      e.preventDefault();
+      const performanceBarCookieName = 'perf_bar_enabled';
+      if (Cookies.get(performanceBarCookieName) === 'true') {
+        Cookies.remove(performanceBarCookieName, { path: '/' });
+      } else {
+        Cookies.set(performanceBarCookieName, true, { path: '/' });
+      }
+      gl.utils.refreshCurrentPage();
+    };
+
     Shortcuts.prototype.toggleMarkdownPreview = function(e) {
       // Check if short-cut was triggered while in Write Mode
       const $target = $(e.target);
diff --git a/app/assets/javascripts/vue_shared/components/time_ago_tooltip.vue b/app/assets/javascripts/vue_shared/components/time_ago_tooltip.vue
index af2b4c6786ed91b6ae040da88a1c7d56384f96fc..1c6ef071a6d2d3bc0a5df213e97708fd4c2e7248 100644
--- a/app/assets/javascripts/vue_shared/components/time_ago_tooltip.vue
+++ b/app/assets/javascripts/vue_shared/components/time_ago_tooltip.vue
@@ -20,12 +20,6 @@ export default {
       default: 'top',
     },
 
-    shortFormat: {
-      type: Boolean,
-      required: false,
-      default: false,
-    },
-
     cssClass: {
       type: String,
       required: false,
@@ -37,18 +31,12 @@ export default {
     tooltipMixin,
     timeagoMixin,
   ],
-
-  computed: {
-    timeagoCssClass() {
-      return this.shortFormat ? 'js-short-timeago' : 'js-timeago';
-    },
-  },
 };
 </script>
 <template>
   <time
-    :class="[timeagoCssClass, cssClass]"
-    class="js-timeago js-timeago-render"
+    :class="cssClass"
+    class="js-vue-timeago"
     :title="tooltipTitle(time)"
     :data-placement="tooltipPlacement"
     data-container="body"
diff --git a/app/assets/stylesheets/framework/dropdowns.scss b/app/assets/stylesheets/framework/dropdowns.scss
index 17f1dc2f4796c2f592cf99c08420e2d39eb3a6dd..cba890ce8317da6860a6a86317609f30a8f6a370 100644
--- a/app/assets/stylesheets/framework/dropdowns.scss
+++ b/app/assets/stylesheets/framework/dropdowns.scss
@@ -48,6 +48,10 @@
     @include chevron-active;
     border-color: $gray-darkest;
   }
+
+  [data-toggle="dropdown"] {
+    outline: 0;
+  }
 }
 
 .dropdown-toggle {
@@ -109,6 +113,7 @@
   &:focus:active {
     @include chevron-active;
     border-color: $dropdown-toggle-active-border-color;
+    outline: 0;
   }
 }
 
diff --git a/app/assets/stylesheets/pages/builds.scss b/app/assets/stylesheets/pages/builds.scss
index e35558ad8e82a0254d7f6f3a0695c1baeaabc02c..a5a876d167b3d4849a01bcbb741089dde22c570a 100644
--- a/app/assets/stylesheets/pages/builds.scss
+++ b/app/assets/stylesheets/pages/builds.scss
@@ -71,7 +71,9 @@
       height: 35px;
       display: flex;
       justify-content: flex-end;
-      border-bottom: 1px outset $white-light;
+      background: $gray-light;
+      border: 1px solid $border-color;
+      color: $gl-text-color;
 
       .truncated-info {
         margin: 0 auto;
@@ -82,7 +84,7 @@
         }
 
         .raw-link {
-          color: inherit;
+          color: $gl-text-color;
           margin-left: 5px;
           text-decoration: underline;
         }
@@ -93,17 +95,25 @@
       display: flex;
       align-self: center;
       font-size: 15px;
+      margin-bottom: 4px;
 
       svg {
         height: 15px;
         display: block;
-        fill: $white-light;
+        fill: $gl-text-color;
       }
 
-      a,
+      .controllers-buttons,
       .btn-scroll {
-        margin: 0 8px;
-        color: $white-light;
+        color: $gl-text-color;
+        height: 15px;
+        vertical-align: middle;
+        padding: 0;
+        width: 12px;
+      }
+
+      .controllers-buttons {
+        margin: 1px 10px;
       }
 
       .btn-scroll.animate {
@@ -137,9 +147,10 @@
     top: 35px;
     left: 10px;
     bottom: 0;
-    overflow-y: hidden;
-    padding-bottom: 20px;
-    padding-right: 20px;
+    overflow-y: scroll;
+    overflow-x: hidden;
+    padding: 10px 20px 20px 5px;
+    white-space: pre;
   }
 
   .environment-information {
diff --git a/app/assets/stylesheets/pages/notes.scss b/app/assets/stylesheets/pages/notes.scss
index e622e5c3f4bc8169c0b901759cf5ee4b5425b4dd..a04424633901eaeacb50f9f6cef03d62466c566b 100644
--- a/app/assets/stylesheets/pages/notes.scss
+++ b/app/assets/stylesheets/pages/notes.scss
@@ -462,7 +462,6 @@ ul.notes {
 
 .more-actions-toggle {
   padding: 0;
-  outline: none;
 
   &:hover .icon,
   &:focus .icon {
diff --git a/app/assets/stylesheets/pages/projects.scss b/app/assets/stylesheets/pages/projects.scss
index a2f781a6a6eada7a965ce64bb2249a0a5a2720dd..062665bc6342f7810fa82cedd695862dbcda09b3 100644
--- a/app/assets/stylesheets/pages/projects.scss
+++ b/app/assets/stylesheets/pages/projects.scss
@@ -769,8 +769,7 @@ pre.light-well {
 }
 
 .project-refs-form .dropdown-menu,
-.dropdown-menu-projects,
-.dropdown-menu-branches {
+.dropdown-menu-projects {
   width: 300px;
 
   @media (min-width: $screen-sm-min) {
diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb
index 47ce21d238ba7e3d4cac21b49bb7b47e409492af..91694ebcd1d3f2baff9b2a79ffea1a876c0adf63 100644
--- a/app/controllers/application_controller.rb
+++ b/app/controllers/application_controller.rb
@@ -9,6 +9,7 @@ class ApplicationController < ActionController::Base
   include SentryHelper
   include WorkhorseHelper
   include EnforcesTwoFactorAuthentication
+  include Peek::Rblineprof::CustomControllerHelpers
 
   before_action :authenticate_user_from_private_token!
   before_action :authenticate_user_from_rss_token!
@@ -18,7 +19,7 @@ class ApplicationController < ActionController::Base
   before_action :ldap_security_check
   before_action :sentry_context
   before_action :default_headers
-  before_action :add_gon_variables
+  before_action :add_gon_variables, unless: -> { request.path.start_with?('/-/peek') }
   before_action :configure_permitted_parameters, if: :devise_controller?
   before_action :require_email, unless: :devise_controller?
 
@@ -63,6 +64,21 @@ class ApplicationController < ActionController::Base
     end
   end
 
+  def peek_enabled?
+    return false unless Gitlab::PerformanceBar.enabled?
+    return false unless current_user
+
+    if RequestStore.active?
+      if RequestStore.store.key?(:peek_enabled)
+        RequestStore.store[:peek_enabled]
+      else
+        RequestStore.store[:peek_enabled] = cookies[:perf_bar_enabled].present?
+      end
+    else
+      cookies[:perf_bar_enabled].present?
+    end
+  end
+
   protected
 
   # This filter handles both private tokens and personal access tokens
diff --git a/app/controllers/concerns/creates_commit.rb b/app/controllers/concerns/creates_commit.rb
index 183eb00ef67bfd2ec54a5ed7e56785fbab7d0cfa..36ad307a93b04e25a64e8d223f6eea158dbb1846 100644
--- a/app/controllers/concerns/creates_commit.rb
+++ b/app/controllers/concerns/creates_commit.rb
@@ -1,11 +1,6 @@
 module CreatesCommit
   extend ActiveSupport::Concern
 
-  def set_start_branch_to_branch_name
-    branch_exists = @repository.find_branch(@branch_name)
-    @start_branch = @branch_name if branch_exists
-  end
-
   def create_commit(service, success_path:, failure_path:, failure_view: nil, success_notice: nil)
     if can?(current_user, :push_code, @project)
       @project_to_commit_into = @project
diff --git a/app/controllers/concerns/spammable_actions.rb b/app/controllers/concerns/spammable_actions.rb
index d0a692070d93ec5ed04c833abd987f934d8e225a..b68d76aeff0591eba73d9833f6f68789770bf38b 100644
--- a/app/controllers/concerns/spammable_actions.rb
+++ b/app/controllers/concerns/spammable_actions.rb
@@ -17,10 +17,18 @@ module SpammableActions
 
   private
 
+  def ensure_spam_config_loaded!
+    return @spam_config_loaded if defined?(@spam_config_loaded)
+
+    @spam_config_loaded = Gitlab::Recaptcha.load_configurations!
+  end
+
   def recaptcha_check_with_fallback(&fallback)
     if spammable.valid?
       redirect_to spammable
     elsif render_recaptcha?
+      ensure_spam_config_loaded!
+
       if params[:recaptcha_verification]
         flash[:alert] = 'There was an error with the reCAPTCHA. Please solve the reCAPTCHA again.'
       end
@@ -35,7 +43,7 @@ module SpammableActions
     default_params = { request: request }
 
     recaptcha_check = params[:recaptcha_verification] &&
-      Gitlab::Recaptcha.load_configurations! &&
+      ensure_spam_config_loaded! &&
       verify_recaptcha
 
     return default_params unless recaptcha_check
diff --git a/app/controllers/projects/application_controller.rb b/app/controllers/projects/application_controller.rb
index cb4bd0ad5f56972902fbcc4d65fe740cc707e397..603a51266da1c37d6e503267fbd6c745b25f79c3 100644
--- a/app/controllers/projects/application_controller.rb
+++ b/app/controllers/projects/application_controller.rb
@@ -80,10 +80,6 @@ class Projects::ApplicationController < ApplicationController
     cookies.permanent[:diff_view] = params.delete(:view) if params[:view].present?
   end
 
-  def builds_enabled
-    return render_404 unless @project.feature_available?(:builds, current_user)
-  end
-
   def require_pages_enabled!
     not_found unless Gitlab.config.pages.enabled
   end
diff --git a/app/controllers/projects/blob_controller.rb b/app/controllers/projects/blob_controller.rb
index d8d14ea1fed5e572a223b7c0b6601702f45aec02..66e6a9a451cef3d7224b15f99752ae860a8348de 100644
--- a/app/controllers/projects/blob_controller.rb
+++ b/app/controllers/projects/blob_controller.rb
@@ -26,8 +26,6 @@ class Projects::BlobController < Projects::ApplicationController
   end
 
   def create
-    set_start_branch_to_branch_name
-
     create_commit(Files::CreateService, success_notice: "The file has been successfully created.",
                                         success_path: -> { namespace_project_blob_path(@project.namespace, @project, File.join(@branch_name, @file_path)) },
                                         failure_view: :new,
diff --git a/app/controllers/projects/branches_controller.rb b/app/controllers/projects/branches_controller.rb
index d8ed470e4613f02e694087e35a3d2d29aafcd868..70b06cfd9b4c5b3547b394b83fe9cacc7d20a2db 100644
--- a/app/controllers/projects/branches_controller.rb
+++ b/app/controllers/projects/branches_controller.rb
@@ -10,10 +10,10 @@ class Projects::BranchesController < Projects::ApplicationController
   def index
     @sort = params[:sort].presence || sort_value_name
     @branches = BranchesFinder.new(@repository, params).execute
+    @branches = Kaminari.paginate_array(@branches).page(params[:page])
 
     respond_to do |format|
       format.html do
-        paginate_branches
         @refs_pipelines = @project.pipelines.latest_successful_for_refs(@branches.map(&:name))
 
         @max_commits = @branches.reduce(0) do |memo, branch|
@@ -22,7 +22,6 @@ class Projects::BranchesController < Projects::ApplicationController
         end
       end
       format.json do
-        paginate_branches unless params[:show_all]
         render json: @branches.map(&:name)
       end
     end
@@ -106,10 +105,6 @@ class Projects::BranchesController < Projects::ApplicationController
     end
   end
 
-  def paginate_branches
-    @branches = Kaminari.paginate_array(@branches).page(params[:page])
-  end
-
   def url_to_autodeploy_setup(project, branch_name)
     namespace_project_new_blob_path(
       project.namespace,
diff --git a/app/controllers/projects/graphs_controller.rb b/app/controllers/projects/graphs_controller.rb
index 43fc0c39801b88b48c54e09c5451a2122719d3f5..df5221fe95f2762550444b440ba38a7ea123745b 100644
--- a/app/controllers/projects/graphs_controller.rb
+++ b/app/controllers/projects/graphs_controller.rb
@@ -5,7 +5,6 @@ class Projects::GraphsController < Projects::ApplicationController
   before_action :require_non_empty_project
   before_action :assign_ref_vars
   before_action :authorize_download_code!
-  before_action :builds_enabled, only: :ci
 
   def show
     respond_to do |format|
diff --git a/app/controllers/projects/pipelines_controller.rb b/app/controllers/projects/pipelines_controller.rb
index 6223e7943f854bda464525d2288350a1c0097f13..8effb792689c9cc114e9f399860c18562f8c64b6 100644
--- a/app/controllers/projects/pipelines_controller.rb
+++ b/app/controllers/projects/pipelines_controller.rb
@@ -4,7 +4,6 @@ class Projects::PipelinesController < Projects::ApplicationController
   before_action :authorize_read_pipeline!
   before_action :authorize_create_pipeline!, only: [:new, :create]
   before_action :authorize_update_pipeline!, only: [:retry, :cancel]
-  before_action :builds_enabled, only: :charts
 
   wrap_parameters Ci::Pipeline
 
diff --git a/app/controllers/projects/tree_controller.rb b/app/controllers/projects/tree_controller.rb
index f8eb8e00a5d3b28571cf040800f0e017c332c6e0..266a15c1cf945589e7e4be328f34a5d1223004f7 100644
--- a/app/controllers/projects/tree_controller.rb
+++ b/app/controllers/projects/tree_controller.rb
@@ -36,7 +36,6 @@ class Projects::TreeController < Projects::ApplicationController
   def create_dir
     return render_404 unless @commit_params.values.all?
 
-    set_start_branch_to_branch_name
     create_commit(Files::CreateDirService,  success_notice: "The directory has been successfully created.",
                                             success_path: namespace_project_tree_path(@project.namespace, @project, File.join(@branch_name, @dir_name)),
                                             failure_path: namespace_project_tree_path(@project.namespace, @project, @ref))
diff --git a/app/helpers/nav_helper.rb b/app/helpers/nav_helper.rb
index 88dfe78c90c6f91c0dead68c4b0bf0fd25c777ce..833d3c36b28a5f19e5a6ec709d0e610c66f49aaf 100644
--- a/app/helpers/nav_helper.rb
+++ b/app/helpers/nav_helper.rb
@@ -27,6 +27,7 @@ module NavHelper
   def nav_header_class
     class_name = ''
     class_name << " with-horizontal-nav" if defined?(nav) && nav
+    class_name << " with-peek" if peek_enabled?
 
     class_name
   end
diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb
index 7441b58fddb9ae539ec0706b9760a4fe3e765115..c11dd49f4a7fb79065d9fbd7670ac72d4be23abd 100644
--- a/app/helpers/projects_helper.rb
+++ b/app/helpers/projects_helper.rb
@@ -218,6 +218,10 @@ module ProjectsHelper
       nav_tabs << :container_registry
     end
 
+    if project.builds_enabled? && can?(current_user, :read_pipeline, project)
+      nav_tabs << :pipelines
+    end
+
     tab_ability_map.each do |tab, ability|
       if can?(current_user, ability, project)
         nav_tabs << tab
@@ -231,7 +235,6 @@ module ProjectsHelper
     {
       environments: :read_environment,
       milestones:   :read_milestone,
-      pipelines:    :read_pipeline,
       snippets:     :read_project_snippet,
       settings:     :admin_project,
       builds:       :read_build,
diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb
index cec1ca89a6a047e3e39837689433cde88389a028..58758f7ca8a940dae1b386bf55f120d67e5d7186 100644
--- a/app/models/ci/build.rb
+++ b/app/models/ci/build.rb
@@ -33,7 +33,7 @@ module Ci
     scope :with_artifacts_not_expired, ->() { with_artifacts.where('artifacts_expire_at IS NULL OR artifacts_expire_at > ?', Time.now) }
     scope :with_expired_artifacts, ->() { with_artifacts.where('artifacts_expire_at < ?', Time.now) }
     scope :last_month, ->() { where('created_at > ?', Date.today - 1.month) }
-    scope :manual_actions, ->() { where(when: :manual).relevant }
+    scope :manual_actions, ->() { where(when: :manual, status: COMPLETED_STATUSES + [:manual]) }
 
     mount_uploader :artifacts_file, ArtifactUploader
     mount_uploader :artifacts_metadata, ArtifactUploader
@@ -109,7 +109,7 @@ module Ci
     end
 
     def playable?
-      action? && manual?
+      action? && (manual? || complete?)
     end
 
     def action?
diff --git a/app/models/environment.rb b/app/models/environment.rb
index 6211a5c1e63701a37b0e9e8b6436a893f4f65f93..d5b974b2d31ba69d376fac3b5d421d34f1618a53 100644
--- a/app/models/environment.rb
+++ b/app/models/environment.rb
@@ -209,7 +209,8 @@ class Environment < ActiveRecord::Base
   def etag_cache_key
     Gitlab::Routing.url_helpers.namespace_project_environments_path(
       project.namespace,
-      project)
+      project,
+      format: :json)
   end
 
   private
diff --git a/app/models/generic_commit_status.rb b/app/models/generic_commit_status.rb
index 8867ba0d2ff5db2300f66b37558108974cac423f..532b8f4ad69b89ba663602aa084ca2a552b879f7 100644
--- a/app/models/generic_commit_status.rb
+++ b/app/models/generic_commit_status.rb
@@ -11,6 +11,7 @@ class GenericCommitStatus < CommitStatus
   def set_default_values
     self.context ||= 'default'
     self.stage ||= 'external'
+    self.stage_idx ||= 1000000
   end
 
   def tags
diff --git a/app/policies/project_policy.rb b/app/policies/project_policy.rb
index 3959b895f44fc1b29fd115201aff1370ff676406..47518dddb61cb80a37fb7d0da419ce665a52dbfd 100644
--- a/app/policies/project_policy.rb
+++ b/app/policies/project_policy.rb
@@ -203,7 +203,7 @@ class ProjectPolicy < BasePolicy
 
     unless project.feature_available?(:builds, user) && repository_enabled
       cannot!(*named_abilities(:build))
-      cannot!(*named_abilities(:pipeline))
+      cannot!(*named_abilities(:pipeline) - [:read_pipeline])
       cannot!(*named_abilities(:pipeline_schedule))
       cannot!(*named_abilities(:environment))
       cannot!(*named_abilities(:deployment))
diff --git a/app/serializers/build_details_entity.rb b/app/serializers/build_details_entity.rb
index 0063920e60398553af49a27c36d3e11ebf57a9c3..7582ab3e08c533f720208f6b199fb9caee591513 100644
--- a/app/serializers/build_details_entity.rb
+++ b/app/serializers/build_details_entity.rb
@@ -1,18 +1,15 @@
-class BuildDetailsEntity < BuildEntity
+class BuildDetailsEntity < JobEntity
   expose :coverage, :erased_at, :duration
   expose :tag_list, as: :tags
-
   expose :user, using: UserEntity
+  expose :runner, using: RunnerEntity
+  expose :pipeline, using: PipelineEntity
 
   expose :erased_by, if: -> (*) { build.erased? }, using: UserEntity
   expose :erase_path, if: -> (*) { build.erasable? && can?(current_user, :update_build, project) } do |build|
     erase_namespace_project_job_path(project.namespace, project, build)
   end
 
-  expose :artifacts, using: BuildArtifactEntity
-  expose :runner, using: RunnerEntity
-  expose :pipeline, using: PipelineEntity
-
   expose :merge_request, if: -> (*) { can?(current_user, :read_merge_request, build.merge_request) } do
     expose :iid do |build|
       build.merge_request.iid
diff --git a/app/serializers/build_serializer.rb b/app/serializers/build_serializer.rb
index 79b670011991c77209624860fa7f5f66d48e0668..bae9932847f272e701872a3882ff36f8f4966367 100644
--- a/app/serializers/build_serializer.rb
+++ b/app/serializers/build_serializer.rb
@@ -1,5 +1,5 @@
 class BuildSerializer < BaseSerializer
-  entity BuildEntity
+  entity JobEntity
 
   def represent_status(resource)
     data = represent(resource, { only: [:status] })
diff --git a/app/serializers/deployment_entity.rb b/app/serializers/deployment_entity.rb
index 8b3de1bed0f2f6039ac125571794f509ad90db2f..e493c9162fd6a1a1a3a58915f5fde0c1d5f8a9ca 100644
--- a/app/serializers/deployment_entity.rb
+++ b/app/serializers/deployment_entity.rb
@@ -24,6 +24,6 @@ class DeploymentEntity < Grape::Entity
 
   expose :user, using: UserEntity
   expose :commit, using: CommitEntity
-  expose :deployable, using: BuildEntity
-  expose :manual_actions, using: BuildEntity
+  expose :deployable, using: JobEntity
+  expose :manual_actions, using: JobEntity
 end
diff --git a/app/serializers/build_entity.rb b/app/serializers/job_entity.rb
similarity index 89%
rename from app/serializers/build_entity.rb
rename to app/serializers/job_entity.rb
index c01efa9dd5c962a0e07e8695f7d61819b09285b1..889fd0c602321fb8a1e41b7bd190f7ce6a9da205 100644
--- a/app/serializers/build_entity.rb
+++ b/app/serializers/job_entity.rb
@@ -1,11 +1,11 @@
-class BuildEntity < Grape::Entity
+class JobEntity < Grape::Entity
   include RequestAwareEntity
 
   expose :id
   expose :name
 
   expose :build_path do |build|
-    path_to(:namespace_project_job, build)
+    build.target_url || path_to(:namespace_project_job, build)
   end
 
   expose :retry_path, if: -> (*) { build&.retryable? } do |build|
diff --git a/app/serializers/job_group_entity.rb b/app/serializers/job_group_entity.rb
index 04487e59009104c7ca9d780e12909c7a4de9f05d..8554de555177e8f3458a4da38d286457f962d89a 100644
--- a/app/serializers/job_group_entity.rb
+++ b/app/serializers/job_group_entity.rb
@@ -4,7 +4,7 @@ class JobGroupEntity < Grape::Entity
   expose :name
   expose :size
   expose :detailed_status, as: :status, with: StatusEntity
-  expose :jobs, with: BuildEntity
+  expose :jobs, with: JobEntity
 
   private
 
diff --git a/app/services/git_push_service.rb b/app/services/git_push_service.rb
index f080e6326a13cbe9c1d8d6fc7793da302e312a05..fb1d4aed58bcf05e32faa90a795f198963440013 100644
--- a/app/services/git_push_service.rb
+++ b/app/services/git_push_service.rb
@@ -101,12 +101,12 @@ class GitPushService < BaseService
     UpdateMergeRequestsWorker
       .perform_async(@project.id, current_user.id, params[:oldrev], params[:newrev], params[:ref])
 
-    SystemHookPushWorker.perform_async(build_push_data.dup, :push_hooks)
-
     EventCreateService.new.push(@project, current_user, build_push_data)
+    Ci::CreatePipelineService.new(@project, current_user, build_push_data).execute(:push)
+    
+    SystemHookPushWorker.perform_async(build_push_data.dup, :push_hooks)
     @project.execute_hooks(build_push_data.dup, :push_hooks)
     @project.execute_services(build_push_data.dup, :push_hooks)
-    Ci::CreatePipelineService.new(@project, current_user, build_push_data).execute(:push)
 
     if push_remove_branch?
       AfterBranchDeleteService
diff --git a/app/services/git_tag_push_service.rb b/app/services/git_tag_push_service.rb
index 7c424fba428ffa8ecad5aad29b306597c5f893ea..9917a39b795997dc2f649c3ba56c80588e5c338d 100644
--- a/app/services/git_tag_push_service.rb
+++ b/app/services/git_tag_push_service.rb
@@ -8,10 +8,12 @@ class GitTagPushService < BaseService
     @push_data = build_push_data
 
     EventCreateService.new.push(project, current_user, @push_data)
+    Ci::CreatePipelineService.new(project, current_user, @push_data).execute(:push)
+
     SystemHooksService.new.execute_hooks(build_system_push_data.dup, :tag_push_hooks)
     project.execute_hooks(@push_data.dup, :tag_push_hooks)
     project.execute_services(@push_data.dup, :tag_push_hooks)
-    Ci::CreatePipelineService.new(project, current_user, @push_data).execute(:push)
+
     ProjectCacheWorker.perform_async(project.id, [], [:commit_count, :repository_size])
 
     true
diff --git a/app/uploaders/artifact_uploader.rb b/app/uploaders/artifact_uploader.rb
index 3bc0408f557e4104f46238b1c6914f16d303c0e8..14addb6cf143d4d17373851ef64a3daba7735567 100644
--- a/app/uploaders/artifact_uploader.rb
+++ b/app/uploaders/artifact_uploader.rb
@@ -23,6 +23,10 @@ class ArtifactUploader < GitlabUploader
     File.join(self.class.local_artifacts_store, 'tmp/cache')
   end
 
+  def work_dir
+    File.join(self.class.local_artifacts_store, 'tmp/work')
+  end
+
   private
 
   def default_local_path
diff --git a/app/uploaders/gitlab_uploader.rb b/app/uploaders/gitlab_uploader.rb
index e4e6d6f46b1248a69187fd2d4b340cacdd8d896f..0da7a025591244539dca6ee92d6ccb0cf57b1235 100644
--- a/app/uploaders/gitlab_uploader.rb
+++ b/app/uploaders/gitlab_uploader.rb
@@ -53,4 +53,27 @@ class GitlabUploader < CarrierWave::Uploader::Base
   def exists?
     file.try(:exists?)
   end
+
+  # Override this if you don't want to save files by default to the Rails.root directory
+  def work_dir
+    # Default path set by CarrierWave:
+    # https://github.com/carrierwaveuploader/carrierwave/blob/v1.0.0/lib/carrierwave/uploader/cache.rb#L182
+    CarrierWave.tmp_path
+  end
+
+  def filename
+    super || file&.filename
+  end
+
+  private
+
+  # To prevent files from moving across filesystems, override the default
+  # implementation:
+  # http://github.com/carrierwaveuploader/carrierwave/blob/v1.0.0/lib/carrierwave/uploader/cache.rb#L181-L183
+  def workfile_path(for_file = original_filename)
+    # To be safe, keep this directory outside of the the cache directory
+    # because calling CarrierWave.clean_cache_files! will remove any files in
+    # the cache directory.
+    File.join(work_dir, @cache_id, version_name.to_s, for_file)
+  end
 end
diff --git a/app/uploaders/lfs_object_uploader.rb b/app/uploaders/lfs_object_uploader.rb
index 02589959c2f2ff62066b954c5c6022f25ad820f2..d11ebf0f9ca445b9d58a90c4fa74ab361df0e788 100644
--- a/app/uploaders/lfs_object_uploader.rb
+++ b/app/uploaders/lfs_object_uploader.rb
@@ -16,16 +16,4 @@ class LfsObjectUploader < GitlabUploader
   def work_dir
     File.join(Gitlab.config.lfs.storage_path, 'tmp', 'work')
   end
-
-  private
-
-  # To prevent LFS files from moving across filesystems, override the default
-  # implementation:
-  # http://github.com/carrierwaveuploader/carrierwave/blob/v1.0.0/lib/carrierwave/uploader/cache.rb#L181-L183
-  def workfile_path(for_file = original_filename)
-    # To be safe, keep this directory outside of the the cache directory
-    # because calling CarrierWave.clean_cache_files! will remove any files in
-    # the cache directory.
-    File.join(work_dir, @cache_id, version_name.to_s, for_file)
-  end
 end
diff --git a/app/views/admin/application_settings/_form.html.haml b/app/views/admin/application_settings/_form.html.haml
index d552704df88056bd3612f409012f349f66da2205..280c5930f3d23ae1d9b9ff2a1b8228e879f26b0d 100644
--- a/app/views/admin/application_settings/_form.html.haml
+++ b/app/views/admin/application_settings/_form.html.haml
@@ -299,8 +299,9 @@
   %fieldset
     %legend Metrics - Prometheus
     %p
-      Setup Prometheus to measure a variety of statistics that partially overlap and complement Influx based metrics.
-      This setting requires a
+      Enable a Prometheus metrics endpoint at `#{metrics_path}` to expose a variety of statistics on the health and performance of GitLab. Additional information on authenticating and connecting to the metrics endpoint is available
+      = link_to 'here', admin_health_check_path
+      \. This setting requires a
       = link_to 'restart', help_page_path('administration/restart_gitlab')
       to take effect.
       = link_to icon('question-circle'), help_page_path('administration/monitoring/performance/introduction')
diff --git a/app/views/help/_shortcuts.html.haml b/app/views/help/_shortcuts.html.haml
index ea8bbe92d865b20a8b5df29d1c103b426ccbaf64..331d118122088f8ea60d5c397e739404755eedca 100644
--- a/app/views/help/_shortcuts.html.haml
+++ b/app/views/help/_shortcuts.html.haml
@@ -27,6 +27,10 @@
                   %td.shortcut
                     .key f
                   %td Focus Filter
+                %tr
+                  %td.shortcut
+                    .key p b
+                  %td Show/hide the Performance Bar
                 %tr
                   %td.shortcut
                     .key ?
diff --git a/app/views/layouts/_head.html.haml b/app/views/layouts/_head.html.haml
index 1ef0d524dbb6908e6844ae1370b75be4039ab19a..eea33b5966f729bb3a81924f76596f24f1227511 100644
--- a/app/views/layouts/_head.html.haml
+++ b/app/views/layouts/_head.html.haml
@@ -28,6 +28,7 @@
   = stylesheet_link_tag "application", media: "all"
   = stylesheet_link_tag "print",       media: "print"
   = stylesheet_link_tag "test",        media: "all" if Rails.env.test?
+  = stylesheet_link_tag 'peek' if peek_enabled?
 
   = Gon::Base.render_data
 
@@ -37,6 +38,7 @@
   = webpack_bundle_tag "main"
   = webpack_bundle_tag "raven" if current_application_settings.clientside_sentry_enabled
   = webpack_bundle_tag "test" if Rails.env.test?
+  = webpack_bundle_tag 'peek' if peek_enabled?
 
   - if content_for?(:page_specific_javascripts)
     = yield :page_specific_javascripts
diff --git a/app/views/layouts/application.html.haml b/app/views/layouts/application.html.haml
index 03688e9ff21dff600e7c31490d043bffaf08066d..2b07273a0a84debea7f08db8b7df92da2355ce0e 100644
--- a/app/views/layouts/application.html.haml
+++ b/app/views/layouts/application.html.haml
@@ -3,6 +3,7 @@
   = render "layouts/head"
   %body{ class: @body_class, data: { page: body_data_page, project: "#{@project.path if @project}", group: "#{@group.path if @group}" } }
     = render "layouts/init_auto_complete" if @gfm_form
+    = render 'peek/bar'
     = render "layouts/header/default", title: header_title
     = render 'layouts/page', sidebar: sidebar, nav: nav
 
diff --git a/app/views/peek/views/_mysql2.html.haml b/app/views/peek/views/_mysql2.html.haml
new file mode 100644
index 0000000000000000000000000000000000000000..ac811a10ef5e93c671be117914944dbea316dd79
--- /dev/null
+++ b/app/views/peek/views/_mysql2.html.haml
@@ -0,0 +1,4 @@
+- local_assigns.fetch(:view)
+
+= render 'peek/views/sql', view: view
+mysql
diff --git a/app/views/peek/views/_pg.html.haml b/app/views/peek/views/_pg.html.haml
new file mode 100644
index 0000000000000000000000000000000000000000..ee94c2f32749aa192a46a8657e6d9068b5284b6f
--- /dev/null
+++ b/app/views/peek/views/_pg.html.haml
@@ -0,0 +1,4 @@
+- local_assigns.fetch(:view)
+
+= render 'peek/views/sql', view: view
+pg
diff --git a/app/views/peek/views/_sql.html.haml b/app/views/peek/views/_sql.html.haml
new file mode 100644
index 0000000000000000000000000000000000000000..16fc010f66f5dfa80bdaf6629ada6030dc5ed86a
--- /dev/null
+++ b/app/views/peek/views/_sql.html.haml
@@ -0,0 +1,13 @@
+%strong
+  %a#peek-show-queries{ href: '#' }
+    %span{ data: { defer_to: "#{view.defer_key}-duration" } }...
+    \/
+    %span{ data: { defer_to: "#{view.defer_key}-calls" } }...
+#modal-peek-pg-queries.modal{ tabindex: -1 }
+  .modal-dialog
+    #modal-peek-pg-queries-content.modal-content
+      .modal-header
+        %a.close{ href: "#", "data-dismiss" => "modal" } ×
+        %h4
+          SQL queries
+      .modal-body{ data: { defer_to: "#{view.defer_key}-queries" } }...
diff --git a/app/views/projects/blob/_remove.html.haml b/app/views/projects/blob/_remove.html.haml
index db6662a95acb953f849467aa5cadde5f9043bcfc..c8ca040621384d2a959fcb06a97311338da109fe 100644
--- a/app/views/projects/blob/_remove.html.haml
+++ b/app/views/projects/blob/_remove.html.haml
@@ -6,7 +6,7 @@
         %h3.page-title Delete #{@blob.name}
 
       .modal-body
-        = form_tag namespace_project_blob_path(@project.namespace, @project, @id), method: :delete, class: 'form-horizontal js-replace-blob-form js-quick-submit js-requires-input' do
+        = form_tag namespace_project_blob_path(@project.namespace, @project, @id), method: :delete, class: 'form-horizontal js-delete-blob-form js-quick-submit js-requires-input' do
           = render 'shared/new_commit_form', placeholder: "Delete #{@blob.name}"
 
           .form-group
@@ -15,4 +15,4 @@
               = link_to "Cancel", '#', class: "btn btn-cancel", "data-dismiss" => "modal"
 
 :javascript
-  new NewCommitForm($('.js-replace-blob-form'))
+  new NewCommitForm($('.js-delete-blob-form'))
diff --git a/app/views/projects/commit/_change.html.haml b/app/views/projects/commit/_change.html.haml
index b5f67cae341319965980ac72e43cc8c78d3b0d75..281d823da52abc1d302398e81908f30ccc1a42fd 100644
--- a/app/views/projects/commit/_change.html.haml
+++ b/app/views/projects/commit/_change.html.haml
@@ -18,14 +18,13 @@
             = label_tag 'start_branch', branch_label, class: 'control-label'
             .col-sm-10
               = hidden_field_tag :start_branch, @project.default_branch, id: 'start_branch'
-              = dropdown_tag(@project.default_branch, options: { title: "Switch branch", filter: true, placeholder: "Search branches", toggle_class: 'js-project-refs-dropdown js-target-branch dynamic', dropdown_class: 'dropdown-menu-selectable', data: { field_name: "start_branch", selected: @project.default_branch, start_branch: @project.default_branch, refs_url: namespace_project_branches_path(@project.namespace, @project), submit_form_on_click: false } })
+              = dropdown_tag(@project.default_branch, options: { title: "Switch branch", filter: true, placeholder: "Search branches", toggle_class: 'js-project-refs-dropdown dynamic', dropdown_class: 'dropdown-menu-selectable', data: { field_name: "start_branch", selected: @project.default_branch, start_branch: @project.default_branch, refs_url: namespace_project_branches_path(@project.namespace, @project), submit_form_on_click: false } })
 
               - if can?(current_user, :push_code, @project)
-                .js-create-merge-request-container
-                  .checkbox
-                    = label_tag do
-                      = check_box_tag 'create_merge_request', 1, true, class: 'js-create-merge-request', id: nil
-                      Start a <strong>new merge request</strong> with these changes
+                .checkbox
+                  = label_tag do
+                    = check_box_tag 'create_merge_request', 1, true, class: 'js-create-merge-request', id: nil
+                    Start a <strong>new merge request</strong> with these changes
               - else
                 = hidden_field_tag 'create_merge_request', 1, id: nil
           .form-actions
@@ -35,6 +34,3 @@
             - unless can?(current_user, :push_code, @project)
               .inline.prepend-left-10
                 = commit_in_fork_help
-
-:javascript
-  new NewCommitForm($('.js-#{type}-form'), 'start_branch')
diff --git a/app/views/projects/jobs/show.html.haml b/app/views/projects/jobs/show.html.haml
index 0d10dfcef7067fb864aa46985a0902ca2b5228f2..987068dc18e906da6c7897a9090061e4c57fc4de 100644
--- a/app/views/projects/jobs/show.html.haml
+++ b/app/views/projects/jobs/show.html.haml
@@ -66,29 +66,24 @@
           .controllers
             - if @build.has_trace?
               = link_to raw_namespace_project_job_path(@project.namespace, @project, @build),
-                      title: 'Open raw trace',
+                      title: 'Show complete raw',
                       data: { placement: 'top', container: 'body' },
-                      class: 'js-raw-link-controller has-tooltip' do
-                = icon('download')
+                      class: 'js-raw-link-controller has-tooltip controllers-buttons' do
+                = icon('file-text-o')
 
             - if can?(current_user, :update_build, @project) && @build.erasable?
               = link_to erase_namespace_project_job_path(@project.namespace, @project, @build),
                         method: :post,
                         data: { confirm: 'Are you sure you want to erase this build?', placement: 'top', container: 'body' },
-                        title: 'Erase Build',
-                        class: 'has-tooltip js-erase-link' do
+                        title: 'Erase job log',
+                        class: 'has-tooltip js-erase-link controllers-buttons' do
                 = icon('trash')
-
-            %button.js-scroll-up.btn-scroll.btn-transparent.btn-blank.has-tooltip{ type: 'button',
-                    disabled: true,
-                    title: 'Scroll Up',
-                    data: { placement: 'top', container: 'body'} }
-              = custom_icon('scroll_up')
-            %button.js-scroll-down.btn-scroll.btn-transparent.btn-blank.has-tooltip{ type: 'button',
-                    disabled: true,
-                    title: 'Scroll Down',
-                    data: { placement: 'top', container: 'body'} }
-              = custom_icon('scroll_down')
+            .has-tooltip.controllers-buttons{ title: 'Scroll to top', data: { placement: 'top', container: 'body'} }
+              %button.js-scroll-up.btn-scroll.btn-transparent.btn-blank{ type: 'button', disabled: true }
+                = custom_icon('scroll_up')
+            .has-tooltip.controllers-buttons{ title: 'Scroll to bottom', data: { placement: 'top', container: 'body'} }
+              %button.js-scroll-down.btn-scroll.btn-transparent.btn-blank{ type: 'button', disabled: true }
+                = custom_icon('scroll_down')
         .bash.sticky.js-scroll-container
           %code.js-build-output
           .build-loader-animation.js-build-refresh
diff --git a/app/views/projects/pipelines/_head.html.haml b/app/views/projects/pipelines/_head.html.haml
index a33da149c6270d23bac595de89b892e1af05335b..d2f0cb0806f88980bf388ee454ebd6ab16f497f9 100644
--- a/app/views/projects/pipelines/_head.html.haml
+++ b/app/views/projects/pipelines/_head.html.haml
@@ -10,7 +10,7 @@
                 Pipelines
 
         - if project_nav_tab? :builds
-          = nav_link(controller: [:builds, :artifacts]) do
+          = nav_link(controller: [:jobs, :artifacts]) do
             = link_to project_jobs_path(@project), title: 'Jobs', class: 'shortcuts-builds' do
               %span
                 Jobs
diff --git a/app/views/shared/_branch_switcher.html.haml b/app/views/shared/_branch_switcher.html.haml
deleted file mode 100644
index 69e3f3042a988eaecf09fb921508c348c479d0f0..0000000000000000000000000000000000000000
--- a/app/views/shared/_branch_switcher.html.haml
+++ /dev/null
@@ -1,8 +0,0 @@
-- dropdown_toggle_text = @branch_name || tree_edit_branch
-= hidden_field_tag 'branch_name', dropdown_toggle_text
-
-.dropdown
-  = dropdown_toggle dropdown_toggle_text, { toggle: 'dropdown', selected: dropdown_toggle_text, field_name: 'branch_name', form_id: '.js-edit-blob-form', refs_url: namespace_project_branches_path(@project.namespace, @project) }, { toggle_class: 'js-project-branches-dropdown js-target-branch' }
-  .dropdown-menu.dropdown-menu-selectable.dropdown-menu-paging.dropdown-menu-branches
-    = render partial: 'shared/projects/blob/branch_page_default'
-    = render partial: 'shared/projects/blob/branch_page_create'
diff --git a/app/views/shared/_new_commit_form.html.haml b/app/views/shared/_new_commit_form.html.haml
index 0b37fe3013b48a490e557b435121240ce1814842..25a56f84ec5f4971a0d726a7fc254741f444bb40 100644
--- a/app/views/shared/_new_commit_form.html.haml
+++ b/app/views/shared/_new_commit_form.html.haml
@@ -7,7 +7,7 @@
     .form-group.branch
       = label_tag 'branch_name', 'Target branch', class: 'control-label'
       .col-sm-10
-        = render 'shared/branch_switcher'
+        = text_field_tag 'branch_name', @branch_name || tree_edit_branch, required: true, class: "form-control js-branch-name ref-name"
 
         .js-create-merge-request-container
           .checkbox
diff --git a/app/views/shared/projects/blob/_branch_page_create.html.haml b/app/views/shared/projects/blob/_branch_page_create.html.haml
deleted file mode 100644
index c279a0d88461e919b33811125a45933f10aad25b..0000000000000000000000000000000000000000
--- a/app/views/shared/projects/blob/_branch_page_create.html.haml
+++ /dev/null
@@ -1,8 +0,0 @@
-.dropdown-page-two.dropdown-new-branch
-  = dropdown_title('Create new branch', back: true)
-  = dropdown_content do
-    %input#new_branch_name.default-dropdown-input.append-bottom-10{ type: "text", placeholder: "Name new branch" }
-      %button.btn.btn-primary.pull-left.js-new-branch-btn{ type: "button" }
-        Create
-      %button.btn.btn-default.pull-right.js-cancel-branch-btn{ type: "button" }
-        Cancel
diff --git a/app/views/shared/projects/blob/_branch_page_default.html.haml b/app/views/shared/projects/blob/_branch_page_default.html.haml
deleted file mode 100644
index 9bf78d10878f613d5d71ec246705f2681b2490b5..0000000000000000000000000000000000000000
--- a/app/views/shared/projects/blob/_branch_page_default.html.haml
+++ /dev/null
@@ -1,10 +0,0 @@
-.dropdown-page-one
-  = dropdown_title "Select branch"
-  = dropdown_filter "Search branches"
-  = dropdown_content
-  = dropdown_loading
-  = dropdown_footer do
-    %ul.dropdown-footer-list
-      %li
-        %a.create-new-branch.dropdown-toggle-page{ href: "#" }
-          Create new branch
diff --git a/app/workers/post_receive.rb b/app/workers/post_receive.rb
index c29571d3c62ab75770bde4a153298fac7e654071..89286595ca6623e27f30a08c5797de2f744c15e4 100644
--- a/app/workers/post_receive.rb
+++ b/app/workers/post_receive.rb
@@ -17,34 +17,18 @@ class PostReceive
     post_received = Gitlab::GitPostReceive.new(project, identifier, changes)
 
     if is_wiki
-      # Nothing defined here yet.
+      process_wiki_changes(post_received)
     else
       process_project_changes(post_received)
-      process_repository_update(post_received)
     end
   end
 
-  def process_repository_update(post_received)
+  private
+
+  def process_project_changes(post_received)
     changes = []
     refs = Set.new
 
-    post_received.changes_refs do |oldrev, newrev, ref|
-      @user ||= post_received.identify(newrev)
-
-      unless @user
-        log("Triggered hook for non-existing user \"#{post_received.identifier}\"")
-        return false
-      end
-
-      changes << Gitlab::DataBuilder::Repository.single_change(oldrev, newrev, ref)
-      refs << ref
-    end
-
-    hook_data = Gitlab::DataBuilder::Repository.update(post_received.project, @user, changes, refs.to_a)
-    SystemHooksService.new.execute_hooks(hook_data, :repository_update_hooks)
-  end
-
-  def process_project_changes(post_received)
     post_received.changes_refs do |oldrev, newrev, ref|
       @user ||= post_received.identify(newrev)
 
@@ -58,10 +42,22 @@ class PostReceive
       elsif Gitlab::Git.branch_ref?(ref)
         GitPushService.new(post_received.project, @user, oldrev: oldrev, newrev: newrev, ref: ref).execute
       end
+
+      changes << Gitlab::DataBuilder::Repository.single_change(oldrev, newrev, ref)
+      refs << ref
     end
+
+    after_project_changes_hooks(post_received, @user, refs.to_a, changes)
   end
 
-  private
+  def after_project_changes_hooks(post_received, user, refs, changes)
+    hook_data = Gitlab::DataBuilder::Repository.update(post_received.project, user, changes, refs)
+    SystemHooksService.new.execute_hooks(hook_data, :repository_update_hooks)
+  end
+
+  def process_wiki_changes(post_received)
+    # Nothing defined here yet.
+  end
 
   # To maintain backwards compatibility, we accept both gl_repository or
   # repository paths as project identifiers. Our plan is to migrate to
diff --git a/changelogs/unreleased/29010-perf-bar.yml b/changelogs/unreleased/29010-perf-bar.yml
new file mode 100644
index 0000000000000000000000000000000000000000..f4167e5562f4c0e7e584ae020cdb2b8574b0c47a
--- /dev/null
+++ b/changelogs/unreleased/29010-perf-bar.yml
@@ -0,0 +1,4 @@
+---
+title: Add an optional performance bar to view performance metrics for the current page
+merge_request: 11439
+author:
diff --git a/changelogs/unreleased/disable-blocked-manual-actions.yml b/changelogs/unreleased/disable-blocked-manual-actions.yml
new file mode 100644
index 0000000000000000000000000000000000000000..a640f61a7ddb10f90d41712fbe426672b1fa5e24
--- /dev/null
+++ b/changelogs/unreleased/disable-blocked-manual-actions.yml
@@ -0,0 +1,4 @@
+---
+title: disable blocked manual actions
+merge_request:
+author:
diff --git a/changelogs/unreleased/dm-fix-parser-cache.yml b/changelogs/unreleased/dm-fix-parser-cache.yml
new file mode 100644
index 0000000000000000000000000000000000000000..31c163b727227a471ef466973a0b77b84ab9755d
--- /dev/null
+++ b/changelogs/unreleased/dm-fix-parser-cache.yml
@@ -0,0 +1,4 @@
+---
+title: Don't return nil for missing objects from parser cache
+merge_request:
+author:
diff --git a/changelogs/unreleased/dm-revert-mr-8427.yml b/changelogs/unreleased/dm-revert-mr-8427.yml
new file mode 100644
index 0000000000000000000000000000000000000000..a91cff2e9cd7077a19d075f3a0260222e3996d2a
--- /dev/null
+++ b/changelogs/unreleased/dm-revert-mr-8427.yml
@@ -0,0 +1,4 @@
+---
+title: Revert 'New file from interface on existing branch'
+merge_request:
+author:
diff --git a/changelogs/unreleased/fix-github-clone-wiki.yml b/changelogs/unreleased/fix-github-clone-wiki.yml
new file mode 100644
index 0000000000000000000000000000000000000000..eadd90e13901220a69edc8826f256af5652ad385
--- /dev/null
+++ b/changelogs/unreleased/fix-github-clone-wiki.yml
@@ -0,0 +1,4 @@
+---
+title: Github - Fix token interpolation when cloning wiki repository
+merge_request:
+author:
diff --git a/changelogs/unreleased/fix-support-for-external-ci-services.yml b/changelogs/unreleased/fix-support-for-external-ci-services.yml
new file mode 100644
index 0000000000000000000000000000000000000000..eecb451925995c5569a446dd31dae9a46fab83f9
--- /dev/null
+++ b/changelogs/unreleased/fix-support-for-external-ci-services.yml
@@ -0,0 +1,4 @@
+---
+title: Fix support for external CI services
+merge_request: 11176
+author:
diff --git a/changelogs/unreleased/instrument-merge-request-diff-load-commits.yml b/changelogs/unreleased/instrument-merge-request-diff-load-commits.yml
new file mode 100644
index 0000000000000000000000000000000000000000..916b182a48b389021b21a9399069ee207c13d4a4
--- /dev/null
+++ b/changelogs/unreleased/instrument-merge-request-diff-load-commits.yml
@@ -0,0 +1,4 @@
+---
+title: Instrument MergeRequestDiff#load_commits
+merge_request:
+author:
diff --git a/changelogs/unreleased/sh-fix-refactor-uploader-work-dir.yml b/changelogs/unreleased/sh-fix-refactor-uploader-work-dir.yml
new file mode 100644
index 0000000000000000000000000000000000000000..255608bd89a61f6570a3e4703e797d3ec32affc4
--- /dev/null
+++ b/changelogs/unreleased/sh-fix-refactor-uploader-work-dir.yml
@@ -0,0 +1,4 @@
+---
+title: Set artifact working directory to be in the destination store to prevent unnecessary I/O
+merge_request:
+author:
diff --git a/changelogs/unreleased/sh-recaptcha-fix-try2.yml b/changelogs/unreleased/sh-recaptcha-fix-try2.yml
new file mode 100644
index 0000000000000000000000000000000000000000..94729252c6f59c33ab080007f78f2772806b8e85
--- /dev/null
+++ b/changelogs/unreleased/sh-recaptcha-fix-try2.yml
@@ -0,0 +1,4 @@
+---
+title: Make sure reCAPTCHA configuration is loaded when spam checks are initiated
+merge_request:
+author:
diff --git a/changelogs/unreleased/zj-raise-etag-route-regex-miss.yml b/changelogs/unreleased/zj-raise-etag-route-regex-miss.yml
new file mode 100644
index 0000000000000000000000000000000000000000..57a5f4e44c0ff92d4fd4a08cd07081bd30b04aa6
--- /dev/null
+++ b/changelogs/unreleased/zj-raise-etag-route-regex-miss.yml
@@ -0,0 +1,4 @@
+---
+title: Fix etag route not being a match for environments
+merge_request:
+author:
diff --git a/config/application.rb b/config/application.rb
index b0533759252e565833fa0f9ec657fb3733ebf1d6..8bbecf3ed0f5287cd2b45e62406b5ea7f27a6526 100644
--- a/config/application.rb
+++ b/config/application.rb
@@ -105,6 +105,7 @@ module Gitlab
     config.assets.precompile << "katex.css"
     config.assets.precompile << "katex.js"
     config.assets.precompile << "xterm/xterm.css"
+    config.assets.precompile << "peek.css"
     config.assets.precompile << "lib/ace.js"
     config.assets.precompile << "vendor/assets/fonts/*"
     config.assets.precompile << "test.css"
diff --git a/config/boot.rb b/config/boot.rb
index f2830ae3166dc7fc2849feff72258dccca1e5f97..17a7114837028aa9ac90e9fea07db8de232ccb17 100644
--- a/config/boot.rb
+++ b/config/boot.rb
@@ -4,3 +4,15 @@ require 'rubygems'
 ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__)
 
 require 'bundler/setup' if File.exist?(ENV['BUNDLE_GEMFILE'])
+
+# Default Bootsnap configuration from https://github.com/Shopify/bootsnap#usage
+require 'bootsnap'
+Bootsnap.setup(
+  cache_dir:            'tmp/cache',
+  development_mode:     ENV['RAILS_ENV'] == 'development',
+  load_path_cache:      true,
+  autoload_paths_cache: true,
+  disable_trace:        false,
+  compile_cache_iseq:   true,
+  compile_cache_yaml:   true
+)
diff --git a/config/environments/production.rb b/config/environments/production.rb
index 82a19085b1df5022dd5349a41ecc48a5797c0f7c..c5cbfcf64cfd3ebfcda210e7b4bcc65956cc7c11 100644
--- a/config/environments/production.rb
+++ b/config/environments/production.rb
@@ -50,7 +50,7 @@ Rails.application.configure do
   # config.logger = ActiveSupport::TaggedLogging.new(SyslogLogger.new)
 
   # Enable serving of images, stylesheets, and JavaScripts from an asset server
-  # config.action_controller.asset_host = "http://assets.example.com"
+  config.action_controller.asset_host = ENV['GITLAB_CDN_HOST'] if ENV['GITLAB_CDN_HOST'].present?
 
   # Precompile additional assets (application.js, application.css, and all non-JS/CSS are already added)
   # config.assets.precompile += %w( search.js )
diff --git a/config/initializers/8_metrics.rb b/config/initializers/8_metrics.rb
index 5e0eefdb154b3a05e9315a63821e5f2fad2dfa8e..508b886d6a0059da659499446897420708f05f6b 100644
--- a/config/initializers/8_metrics.rb
+++ b/config/initializers/8_metrics.rb
@@ -113,6 +113,9 @@ def instrument_classes(instrumentation)
 
   # This is a Rails scope so we have to instrument it manually.
   instrumentation.instrument_method(Project, :visible_to_user)
+
+  # Needed for https://gitlab.com/gitlab-org/gitlab-ce/issues/30224#note_32306159
+  instrumentation.instrument_instance_method(MergeRequestDiff, :load_commits)
 end
 # rubocop:enable Metrics/AbcSize
 
diff --git a/config/initializers/peek.rb b/config/initializers/peek.rb
new file mode 100644
index 0000000000000000000000000000000000000000..65432caac2a66a2995b4131bfa13fb6b53857a67
--- /dev/null
+++ b/config/initializers/peek.rb
@@ -0,0 +1,32 @@
+Rails.application.config.peek.adapter = :redis, { client: ::Redis.new(Gitlab::Redis.params) }
+
+Peek.into Peek::Views::Host
+Peek.into Peek::Views::PerformanceBar
+if Gitlab::Database.mysql?
+  require 'peek-mysql2'
+  PEEK_DB_CLIENT = ::Mysql2::Client
+  PEEK_DB_VIEW = Peek::Views::Mysql2
+else
+  require 'peek-pg'
+  PEEK_DB_CLIENT = ::PG::Connection
+  PEEK_DB_VIEW = Peek::Views::PG
+end
+Peek.into PEEK_DB_VIEW
+Peek.into Peek::Views::Redis
+Peek.into Peek::Views::Sidekiq
+Peek.into Peek::Views::Rblineprof
+Peek.into Peek::Views::GC
+
+# rubocop:disable Style/ClassAndModuleCamelCase
+class PEEK_DB_CLIENT
+  class << self
+    attr_accessor :query_details
+  end
+  self.query_details = Concurrent::Array.new
+end
+
+PEEK_DB_VIEW.prepend ::Gitlab::PerformanceBar::PeekQueryTracker
+
+class Peek::Views::PerformanceBar::ProcessUtilization
+  prepend ::Gitlab::PerformanceBar::PeekPerformanceBarWithRackBody
+end
diff --git a/config/routes.rb b/config/routes.rb
index d909be38b42c441f7c5d291622ff53df01f39523..4fd6cb5d4397afff2a753d47d4ef234be652a389 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -42,6 +42,7 @@ Rails.application.routes.draw do
     get 'liveness' => 'health#liveness'
     get 'readiness' => 'health#readiness'
     resources :metrics, only: [:index]
+    mount Peek::Railtie => '/peek'
   end
 
   # Koding route
diff --git a/config/webpack.config.js b/config/webpack.config.js
index 7501acb763384fc676367d1eade4926beaf232bd..b84d6bb38d0fc7b535bf7717811ef0ae490d845d 100644
--- a/config/webpack.config.js
+++ b/config/webpack.config.js
@@ -18,6 +18,15 @@ var DEV_SERVER_LIVERELOAD = process.env.DEV_SERVER_LIVERELOAD !== 'false';
 var WEBPACK_REPORT = process.env.WEBPACK_REPORT;
 var NO_COMPRESSION = process.env.NO_COMPRESSION;
 
+// optional dependency `node-zopfli` is unavailable on CentOS 6
+var ZOPFLI_AVAILABLE;
+try {
+  require.resolve('node-zopfli');
+  ZOPFLI_AVAILABLE = true;
+} catch(err) {
+  ZOPFLI_AVAILABLE = false;
+}
+
 var config = {
   // because sqljs requires fs.
   node: {
@@ -68,6 +77,7 @@ var config = {
     raven:                './raven/index.js',
     vue_merge_request_widget: './vue_merge_request_widget/index.js',
     test:                 './test.js',
+    peek:                 './peek.js',
   },
 
   output: {
@@ -224,7 +234,7 @@ if (IS_PRODUCTION) {
     config.plugins.push(
       new CompressionPlugin({
         asset: '[path].gz[query]',
-        algorithm: 'zopfli',
+        algorithm: ZOPFLI_AVAILABLE ? 'zopfli' : 'gzip',
       })
     );
   }
diff --git a/doc/administration/environment_variables.md b/doc/administration/environment_variables.md
index b6676026d0618e1f684acceb4b51f51198b92db3..9bcd13a52f79e62b07e7748b71e20f0822bfdbfb 100644
--- a/doc/administration/environment_variables.md
+++ b/doc/administration/environment_variables.md
@@ -13,6 +13,7 @@ override certain values.
 
 Variable | Type | Description
 -------- | ---- | -----------
+`GITLAB_CDN_HOST`                          | string  | Sets the hostname for a CDN to serve static assets (e.g. `mycdnsubdomain.fictional-cdn.com`)
 `GITLAB_ROOT_PASSWORD`                     | string  | Sets the password for the `root` user on installation
 `GITLAB_HOST`                              | string  | The full URL of the GitLab server (including `http://` or `https://`)
 `RAILS_ENV`                                | string  | The Rails environment; can be one of `production`, `development`, `staging` or `test`
@@ -58,6 +59,9 @@ to the naming scheme `GITLAB_#{name in 1_settings.rb in upper case}`.
 
 ## Omnibus configuration
 
+To set environment variables, follow [these
+instructions](https://docs.gitlab.com/omnibus/settings/environment-variables.html).
+
 It's possible to preconfigure the GitLab docker image by adding the environment
 variable `GITLAB_OMNIBUS_CONFIG` to the `docker run` command.
 For more information see the ['preconfigure-docker-container' section in the Omnibus documentation](http://docs.gitlab.com/omnibus/docker/#preconfigure-docker-container).
diff --git a/doc/administration/raketasks/github_import.md b/doc/administration/raketasks/github_import.md
index affb4d17861f7ebbd6d8a4e0b89fb232256173d0..04c70c3644ec741ecb22ac8d9ee7d13c3d95c37a 100644
--- a/doc/administration/raketasks/github_import.md
+++ b/doc/administration/raketasks/github_import.md
@@ -3,7 +3,7 @@
 >**Note:**
 >
 >  - [Introduced][ce-10308] in GitLab 9.1.
->  - You need a personal access token in order to retrieve and import GitHub 
+>  - You need a personal access token in order to retrieve and import GitHub
 >    projects. You can get it from: https://github.com/settings/tokens
 >  - You also need to pass an username as the second argument to the rake task
 >    which will become the owner of the project.
@@ -19,7 +19,7 @@ bundle exec rake import:github[access_token,root,foo/bar] RAILS_ENV=production
 ```
 
 In this case, `access_token` is your GitHub personal access token, `root`
-is your GitLab username, and  `foo/bar` is the new GitLab namespace/project that 
+is your GitLab username, and  `foo/bar` is the new GitLab namespace/project that
 will get created from your GitHub project. Subgroups are also possible: `foo/foo/bar`.
 
 
diff --git a/doc/api/README.md b/doc/api/README.md
index 2175b305e02deb9d2ab877dd67aaf89a14e7599f..4f189c16673bfc6fdf866874db9c7498a5c348ed 100644
--- a/doc/api/README.md
+++ b/doc/api/README.md
@@ -55,28 +55,35 @@ following locations:
 - [V3 to V4](v3_to_v4.md)
 - [Version](version.md)
 
+The following documentation is for the [internal CI API](ci/README.md):
+
+- [Builds](ci/builds.md)
+- [Runners](ci/runners.md)
+
 ## Road to GraphQL
 
-API v4 will be the last REST API that we support. Going forward, we will start
-on moving to GraphQL and deprecate the use of controller-specific
-endpoints. GraphQL has a number of benefits:
+Going forward, we will start on moving to
+[GraphQL](http://graphql.org/learn/best-practices/) and deprecate the use of
+controller-specific endpoints. GraphQL has a number of benefits:
 
 1. We avoid having to maintain two different APIs.
 2. Callers of the API can request only what they need.
+3. It is versioned by default.
 
-### Internal CI API
+It will co-exist with the current v4 REST API. If we have a v5 API, this should
+be a compatibility layer on top of GraphQL.
 
-The following documentation is for the [internal CI API](ci/README.md):
+## Authentication
 
-- [Builds](ci/builds.md)
-- [Runners](ci/runners.md)
+Most API requests require authentication via a session cookie or token. For
+those cases where it is not required, this will be mentioned in the documentation
+for each individual endpoint. For example, the [`/projects/:id` endpoint](projects.md).
 
-## Authentication
+There are three types of access tokens available:
 
-Most API requests require authentication via a session cookie or token. For those cases where it is not required, this will be mentioned in the documentation 
-for each individual endpoint. For example, the [`/projects/:id` endpoint](projects.md). 
-There are three types of tokens available: private tokens, OAuth 2 tokens, and personal
-access tokens.
+1. [OAuth2 tokens](#oauth2-tokens)
+1. [Private tokens](#private-tokens)
+1. [Personal access tokens](#personal-access-tokens)
 
 If authentication information is invalid or omitted, an error message will be
 returned with status code `401`:
@@ -87,20 +94,13 @@ returned with status code `401`:
 }
 ```
 
-### Session Cookie
+### Session cookie
 
 When signing in to GitLab as an ordinary user, a `_gitlab_session` cookie is
 set. The API will use this cookie for authentication if it is present, but using
 the API to generate a new session cookie is currently not supported.
 
-### Private Tokens
-
-You need to pass a `private_token` parameter via query string or header. If passed as a
-header, the header name must be `PRIVATE-TOKEN` (uppercase and with a dash instead of
-an underscore). You can find or reset your private token in your account page
-(`/profile/account`).
-
-### OAuth 2 Tokens
+### OAuth2 tokens
 
 You can use an OAuth 2 token to authenticate with the API by passing it either in the
 `access_token` parameter or in the `Authorization` header.
@@ -113,30 +113,31 @@ curl --header "Authorization: Bearer OAUTH-TOKEN" https://gitlab.example.com/api
 
 Read more about [GitLab as an OAuth2 client](oauth2.md).
 
-### Personal Access Tokens
+### Private tokens
 
-> [Introduced][ce-3749] in GitLab 8.8.
+Private tokens provide full access to the GitLab API. Anyone with access to
+them can interact with GitLab as if they were you. You can find or reset your
+private token in your account page (`/profile/account`).
 
-You can create as many personal access tokens as you like from your GitLab
-profile (`/profile/personal_access_tokens`); perhaps one for each application
-that needs access to the GitLab API.
+For examples of usage, [read the basic usage section](#basic-usage).
 
-Once you have your token, pass it to the API using either the `private_token`
-parameter or the `PRIVATE-TOKEN` header.
+### Personal access tokens
 
-> [Introduced][ce-5951] in GitLab 8.15.
+Instead of using your private token which grants full access to your account,
+personal access tokens could be a better fit because of their granular
+permissions.
 
-Personal Access Tokens can be created with one or more scopes that allow various actions
-that a given token can perform. Although there are only two scopes available at the
-moment – `read_user` and `api` – the groundwork has been laid to add more scopes easily.
+Once you have your token, pass it to the API using either the `private_token`
+parameter or the `PRIVATE-TOKEN` header. For examples of usage,
+[read the basic usage section](#basic-usage).
 
-At any time you can revoke any personal access token by just clicking **Revoke**.
+[Read more about personal access tokens.][pat]
 
 ### Impersonation tokens
 
 > [Introduced][ce-9099] in GitLab 9.0. Needs admin permissions.
 
-Impersonation tokens are a type of [Personal Access Token](#personal-access-tokens)
+Impersonation tokens are a type of [personal access token][pat]
 that can only be created by an admin for a specific user.
 
 They are a better alternative to using the user's password/private token
@@ -145,9 +146,11 @@ or private token, since the password/token can change over time. Impersonation
 tokens are a great fit if you want to build applications or tools which
 authenticate with the API as a specific user.
 
-For more information about the usage please refer to the
+For more information, refer to the
 [users API](users.md#retrieve-user-impersonation-tokens) docs.
 
+For examples of usage, [read the basic usage section](#basic-usage).
+
 ### Sudo
 
 > Needs admin permissions.
@@ -200,11 +203,16 @@ GET /projects?private_token=9koXpg98eAheJpvBs5tK&sudo=23
 curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" --header "SUDO: 23" "https://gitlab.example.com/api/v4/projects"
 ```
 
-## Basic Usage
+## Basic usage
 
 API requests should be prefixed with `api` and the API version. The API version
 is defined in [`lib/api.rb`][lib-api-url].
 
+For endpoints that require [authentication](#authentication), you need to pass
+a `private_token` parameter via query string or header. If passed as a header,
+the header name must be `PRIVATE-TOKEN` (uppercase and with a dash instead of
+an underscore).
+
 Example of a valid API request:
 
 ```
@@ -217,6 +225,12 @@ Example of a valid API request using cURL and authentication via header:
 curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/projects"
 ```
 
+Example of a valid API request using cURL and authentication via a query string:
+
+```shell
+curl "https://gitlab.example.com/api/v4/projects?private_token=9koXpg98eAheJpvBs5tK"
+```
+
 The API uses JSON to serialize data. You don't need to specify `.json` at the
 end of an API URL.
 
@@ -432,3 +446,4 @@ programming languages. Visit the [GitLab website] for a complete list.
 [ce-3749]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/3749
 [ce-5951]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/5951
 [ce-9099]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/9099
+[pat]: ../user/profile/personal_access_tokens.md
diff --git a/doc/api/oauth2.md b/doc/api/oauth2.md
index 46fe64d382e6ef4ad775a9daecee665f2dcf90c3..07cb64cb373aeb302cc9da6376af20d20b0667e1 100644
--- a/doc/api/oauth2.md
+++ b/doc/api/oauth2.md
@@ -134,4 +134,4 @@ access_token = client.password.get_token('user@example.com', 'secret')
 puts access_token.token
 ```
 
-[personal access tokens]: ./README.md#personal-access-tokens
\ No newline at end of file
+[personal access tokens]: ../user/profile/personal_access_tokens.md
diff --git a/doc/api/session.md b/doc/api/session.md
index 7dd504b67c57015d9a484063905e229ac34d9110..f79eac11689f4fe68de2162ad74e048db87fc91e 100644
--- a/doc/api/session.md
+++ b/doc/api/session.md
@@ -1,11 +1,9 @@
 # Session API
 
-## Deprecation Notice
-
-1. Starting in GitLab 8.11, this feature has been *disabled* for users with two-factor authentication turned on.
-2. These users can access the API using [personal access tokens] instead.
-
----
+>**Deprecation notice:**
+Starting in GitLab 8.11, this feature has been **disabled** for users with
+[two-factor authentication][2fa] turned on. These users can access the API
+using [personal access tokens] instead.
 
 You can login with both GitLab and LDAP credentials in order to obtain the
 private token.
@@ -52,4 +50,5 @@ Example response:
 }
 ```
 
-[personal access tokens]: ./README.md#personal-access-tokens
+[2fa]: ../user/profile/account/two_factor_authentication.md
+[personal access tokens]: ../user/profile/personal_access_tokens.md
diff --git a/doc/api/users.md b/doc/api/users.md
index f4167ba2605af69f9da66fe73cb4ba47119f96eb..91ce4f6dac3511c047b4a982e089f7745597ede0 100644
--- a/doc/api/users.md
+++ b/doc/api/users.md
@@ -804,7 +804,7 @@ Example response:
 
 It creates a new impersonation token. Note that only administrators can do this.
 You are only able to create impersonation tokens to impersonate the user and perform
-both API calls and Git reads and writes. The user will not see these tokens in his profile
+both API calls and Git reads and writes. The user will not see these tokens in their profile
 settings page.
 
 ```
diff --git a/doc/ci/docker/using_docker_build.md b/doc/ci/docker/using_docker_build.md
index 408d46a756c5464ffa9e207cd1f87724eec0a833..f7c2a0ef0ca1cb20ec98c9bd1624dfa0e12af89b 100644
--- a/doc/ci/docker/using_docker_build.md
+++ b/doc/ci/docker/using_docker_build.md
@@ -282,9 +282,9 @@ which can be avoided if a different driver is used, for example `overlay`.
 
 > **Notes:**
 - This feature requires GitLab 8.8 and GitLab Runner 1.2.
-- Starting from GitLab 8.12, if you have 2FA enabled in your account, you need
-  to pass a personal access token instead of your password in order to login to
-  GitLab's Container Registry.
+- Starting from GitLab 8.12, if you have [2FA] enabled in your account, you need
+  to pass a [personal access token][pat] instead of your password in order to
+  login to GitLab's Container Registry.
 
 Once you've built a Docker image, you can push it up to the built-in
 [GitLab Container Registry](../../user/project/container_registry.md). For example,
@@ -409,3 +409,5 @@ Some things you should be aware of when using the Container Registry:
 
 [docker-in-docker]: https://blog.docker.com/2013/09/docker-can-now-run-within-docker/
 [docker-cap]: https://docs.docker.com/engine/reference/run/#runtime-privilege-and-linux-capabilities
+[2fa]: ../../user/profile/account/two_factor_authentication.md
+[pat]: ../../user/profile/personal_access_tokens.md
diff --git a/doc/user/admin_area/monitoring/convdev.md b/doc/user/admin_area/monitoring/convdev.md
new file mode 100644
index 0000000000000000000000000000000000000000..3d93c7557a487263ac7a8264f2ba1ece9fbb3209
--- /dev/null
+++ b/doc/user/admin_area/monitoring/convdev.md
@@ -0,0 +1,29 @@
+# Conversational Development Index
+
+> [Introduced][ce-30469] in GitLab 9.3.
+
+Conversational Development Index (ConvDev) gives you an overview of your entire
+instance's feature usage, from idea to production. It looks at your usage in the
+past 30 days, averaged over the number of active users in that time period. It also
+provides a lead score per feature, which is calculated based on GitLab's analysis
+of top performing instances, based on [usage ping data][ping] that GitLab has
+collected. Your score is compared to the lead score, expressed as a percentage.
+The overall index score is an average over all your feature scores.
+
+![ConvDev index](img/convdev_index.png)
+
+The page also provides helpful links to articles and GitLab docs, to help you
+improve your scores.
+
+Your GitLab instance's usage ping must be activated in order to use this feature.
+Usage ping data is aggregated on GitLab's servers for analysis. Your usage
+information is **not sent** to any other GitLab instances.
+
+If you have just started using GitLab, it may take a few weeks for data to be
+collected before this feature is available.
+
+This feature is accessible only to a system admin, at
+**Admin area > Monitoring > ConvDev Index**.
+
+[ce-30469]: https://gitlab.com/gitlab-org/gitlab-ce/issues/30469
+[ping]: ../settings/usage_statistics.md#usage-ping
diff --git a/doc/user/admin_area/monitoring/img/convdev_index.png b/doc/user/admin_area/monitoring/img/convdev_index.png
new file mode 100644
index 0000000000000000000000000000000000000000..4e47ff2228d090ef86b1c8d0bc1231b4d890f512
Binary files /dev/null and b/doc/user/admin_area/monitoring/img/convdev_index.png differ
diff --git a/doc/user/admin_area/settings/usage_statistics.md b/doc/user/admin_area/settings/usage_statistics.md
index f3745d0efa7eb919ae2690ea6b01828bd9c50625..d874688cc29ab1142b786456517fff5fad266a4b 100644
--- a/doc/user/admin_area/settings/usage_statistics.md
+++ b/doc/user/admin_area/settings/usage_statistics.md
@@ -3,7 +3,8 @@
 GitLab Inc. will periodically collect information about your instance in order
 to perform various actions.
 
-All statistics are opt-out, you can disable them from the admin panel.
+All statistics are opt-out, you can enable/disable them from the admin panel
+under **Admin area > Settings > Usage statistics**.
 
 ## Version check
 
diff --git a/doc/user/profile/account/two_factor_authentication.md b/doc/user/profile/account/two_factor_authentication.md
index fb69d934ae17c889ca9df187c1eb0eecd2ac2940..590c3f862fb64bdf0be8dad308791f44f3ee0d23 100644
--- a/doc/user/profile/account/two_factor_authentication.md
+++ b/doc/user/profile/account/two_factor_authentication.md
@@ -125,23 +125,14 @@ applications and U2F devices.
 ## Personal access tokens
 
 When 2FA is enabled, you can no longer use your normal account password to
-authenticate with Git over HTTPS on the command line, you must use a personal
-access token instead.
-
-1. Log in to your GitLab account.
-1. Go to your **Profile Settings**.
-1. Go to **Access Tokens**.
-1. Choose a name and expiry date for the token.
-1. Click on **Create Personal Access Token**.
-1. Save the personal access token somewhere safe.
-
-When using Git over HTTPS on the command line, enter the personal access token
-into the password field.
+authenticate with Git over HTTPS on the command line or when using
+[GitLab's API][api], you must use a [personal access token][pat] instead.
 
 ## Recovery options
 
 To disable two-factor authentication on your account (for example, if you
 have lost your code generation device) you can:
+
 * [Use a saved recovery code](#use-a-saved-recovery-code)
 * [Generate new recovery codes using SSH](#generate-new-recovery-codes-using-ssh)
 * [Ask a GitLab administrator to disable two-factor authentication on your account](#ask-a-gitlab-administrator-to-disable-two-factor-authentication-on-your-account)
@@ -154,8 +145,9 @@ codes. If you saved these codes, you can use one of them to sign in.
 To use a recovery code, enter your username/email and password on the GitLab
 sign-in page. When prompted for a two-factor code, enter the recovery code.
 
-> **Note:** Once you use a recovery code, you cannot re-use it. You can still
- use the other recovery codes you saved.
+>**Note:**
+Once you use a recovery code, you cannot re-use it. You can still use the other
+recovery codes you saved.
 
 ### Generate new recovery codes using SSH
 
@@ -190,11 +182,14 @@ a new set of recovery codes with SSH.
     two-factor code. Then, visit your Profile Settings and add a new device
     so you do not lose access to your account again.
     ```
-3. Go to the GitLab sign-in page and enter your username/email and password. When prompted for a two-factor code, enter one of the recovery codes obtained
-from the command-line output.
 
-> **Note:** After signing in, visit your **Profile Settings -> Account**  immediately to set up two-factor authentication with a new
-  device.
+3. Go to the GitLab sign-in page and enter your username/email and password.
+   When prompted for a two-factor code, enter one of the recovery codes obtained
+   from the command-line output.
+
+>**Note:**
+After signing in, visit your **Profile settings > Account**  immediately to set
+up two-factor authentication with a new device.
 
 ### Ask a GitLab administrator to disable two-factor authentication on your account
 
@@ -206,23 +201,23 @@ Sign in and re-enable two-factor authentication as soon as possible.
 ## Note to GitLab administrators
 
 - You need to take special care to that 2FA keeps working after
-[restoring a GitLab backup](../../../raketasks/backup_restore.md).
-
+  [restoring a GitLab backup](../../../raketasks/backup_restore.md).
 - To ensure 2FA authorizes correctly with TOTP server, you may want to ensure
-your GitLab server's time is synchronized via a service like NTP.  Otherwise,
-you may have cases where authorization always fails because of time differences.
-
-[Google Authenticator]: https://support.google.com/accounts/answer/1066447?hl=en
-[FreeOTP]: https://freeotp.github.io/
-[YubiKey]: https://www.yubico.com/products/yubikey-hardware/
-
+  your GitLab server's time is synchronized via a service like NTP.  Otherwise,
+  you may have cases where authorization always fails because of time differences.
 - The GitLab U2F implementation does _not_ work when the GitLab instance is accessed from
-multiple hostnames, or FQDNs. Each U2F registration is linked to the _current hostname_ at 
-the time of registration, and cannot be used for other hostnames/FQDNs.
+  multiple hostnames, or FQDNs. Each U2F registration is linked to the _current hostname_ at
+  the time of registration, and cannot be used for other hostnames/FQDNs.
 
     For example, if a user is trying to access a GitLab instance from `first.host.xyz` and `second.host.xyz`:
 
     - The user logs in via `first.host.xyz` and registers their U2F key.
     - The user logs out and attempts to log in via `first.host.xyz` - U2F authentication suceeds.
-    - The user logs out and attempts to log in via `second.host.xyz` - U2F authentication fails, because 
+    - The user logs out and attempts to log in via `second.host.xyz` - U2F authentication fails, because
     the U2F key has only been registered on `first.host.xyz`.
+
+[Google Authenticator]: https://support.google.com/accounts/answer/1066447?hl=en
+[FreeOTP]: https://freeotp.github.io/
+[YubiKey]: https://www.yubico.com/products/yubikey-hardware/
+[api]: ../../../api/README.md
+[pat]: ../personal_access_tokens.md
diff --git a/doc/user/profile/img/personal_access_tokens.png b/doc/user/profile/img/personal_access_tokens.png
new file mode 100644
index 0000000000000000000000000000000000000000..6aa63dbe3426a674436a49a7ac67f313aab0c55d
Binary files /dev/null and b/doc/user/profile/img/personal_access_tokens.png differ
diff --git a/doc/user/profile/personal_access_tokens.md b/doc/user/profile/personal_access_tokens.md
new file mode 100644
index 0000000000000000000000000000000000000000..9488ce1ef300348d9ba775b8c596d94a5ac4f5be
--- /dev/null
+++ b/doc/user/profile/personal_access_tokens.md
@@ -0,0 +1,57 @@
+# Personal access tokens
+
+> [Introduced][ce-3749] in GitLab 8.8.
+
+Personal access tokens are useful if you need access to the [GitLab API][api].
+Instead of using your private token which grants full access to your account,
+personal access tokens could be a better fit because of their
+[granular permissions](#limiting-scopes-of-a-personal-access-token).
+
+You can also use them to authenticate against Git over HTTP. They are the only
+accepted method of authentication when you have
+[Two-Factor Authentication (2FA)][2fa] enabled.
+
+Once you have your token, [pass it to the API][usage] using either the
+`private_token` parameter or the `PRIVATE-TOKEN` header.
+
+## Creating a personal access token
+
+You can create as many personal access tokens as you like from your GitLab
+profile.
+
+1. Log in to your GitLab account.
+1. Go to your **Profile settings**.
+1. Go to **Access tokens**.
+1. Choose a name and optionally an expiry date for the token.
+1. Choose the [desired scopes](#limiting-scopes-of-a-personal-access-token).
+1. Click on **Create personal access token**.
+1. Save the personal access token somewhere safe. Once you leave or refresh
+   the page, you won't be able to access it again.
+
+![Personal access tokens page](img/personal_access_tokens.png)
+
+## Revoking a personal access token
+
+At any time, you can revoke any personal access token by just clicking the
+respective **Revoke** button under the 'Active personal access tokens' area.
+
+## Limiting scopes of a personal access token
+
+Personal access tokens can be created with one or more scopes that allow various
+actions that a given token can perform. The available scopes are depicted in
+the following table.
+
+| Scope | Description |
+| ----- | ----------- |
+|`read_user` | Allows access to the read-only endpoints under `/users`. Essentially, any of the `GET` requests in the [Users API][users] are allowed ([introduced][ce-5951] in GitLab 8.15). |
+| `api` | Grants complete access to the API (read/write) ([introduced][ce-5951] in GitLab 8.15). Required for accessing Git repositories over HTTP when 2FA is enabled. |
+| `read_registry` | Allows to read [container registry] images if a project is private and authorization is required ([introduced][ce-11845] in GitLab 9.3). |
+
+[2fa]: ../account/two_factor_authentication.md
+[api]: ../../api/README.md
+[ce-3749]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/3749
+[ce-5951]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/5951
+[ce-11845]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/11845
+[container registry]: ../project/container_registry.md
+[users]: ../../api/users.md
+[usage]: ../../api/README.md#basic-usage
diff --git a/doc/user/project/container_registry.md b/doc/user/project/container_registry.md
index 10c281448a38c00afd02da7695160150939e6dc7..c6862f48040ff167c9a52bffd797bf137e5ea78d 100644
--- a/doc/user/project/container_registry.md
+++ b/doc/user/project/container_registry.md
@@ -8,8 +8,8 @@
   Registry across your GitLab instance, visit the
   [administrator documentation](../../administration/container_registry.md).
 - Starting from GitLab 8.12, if you have 2FA enabled in your account, you need
-  to pass a personal access token instead of your password in order to login to
-  GitLab's Container Registry.
+  to pass a [personal access token][pat] instead of your password in order to
+  login to GitLab's Container Registry.
 - Multiple level image names support was added in GitLab 9.1
 
 With the Docker Container Registry integrated into GitLab, every project can
@@ -106,12 +106,11 @@ and [Using the GitLab Container Registry documentation](../../ci/docker/using_do
 
 ## Using with private projects
 
-If a project is private, credentials will need to be provided for authorization.
-The preferred way to do this, is by using personal access tokens, which can be
-created under `/profile/personal_access_tokens`. The minimal scope needed is:
-`read_registry`.
+> [Introduced][ce-11845] in GitLab 9.3.
 
-This feature was introduced in GitLab 9.3.
+If a project is private, credentials will need to be provided for authorization.
+The preferred way to do this, is by using [personal access tokens][pat].
+The minimal scope needed is `read_registry`.
 
 ## Troubleshooting the GitLab Container Registry
 
@@ -256,4 +255,6 @@ The solution: check the [IAM permissions again](https://docs.docker.com/registry
 Once the right permissions were set, the error will go away.
 
 [ce-4040]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/4040
+[ce-11845]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/11845
 [docker-docs]: https://docs.docker.com/engine/userguide/intro/
+[pat]: ../profile/personal_access_tokens.md
diff --git a/doc/user/project/integrations/img/jira_service_page.png b/doc/user/project/integrations/img/jira_service_page.png
index c74351b57b832eb6b3501d9dd016bd1eb571000f..e69376f74c4b267e52f969d5e79ae769bdfd53b5 100644
Binary files a/doc/user/project/integrations/img/jira_service_page.png and b/doc/user/project/integrations/img/jira_service_page.png differ
diff --git a/doc/user/project/integrations/img/merge_request_performance.png b/doc/user/project/integrations/img/merge_request_performance.png
index 93b2626fed7868a36cec5138cc4f5f245edaa331..eba6515a6ae01a9a9d33c9522fd7db083b5ed306 100644
Binary files a/doc/user/project/integrations/img/merge_request_performance.png and b/doc/user/project/integrations/img/merge_request_performance.png differ
diff --git a/doc/user/project/integrations/jira.md b/doc/user/project/integrations/jira.md
index a048260b033ad3d0da7a068234badf3d2f11f199..d54c1fc78b489b46e05d6b71fea5b9862f48ee0d 100644
--- a/doc/user/project/integrations/jira.md
+++ b/doc/user/project/integrations/jira.md
@@ -98,14 +98,14 @@ in the table below.
 | Field | Description |
 | ----- | ----------- |
 | `Web URL` | The base URL to the JIRA instance web interface which is being linked to this GitLab project. E.g., `https://jira.example.com`. |
-| `JIRA API URL` | The base URL to the JIRA instance API. Web URL value will be used if not set. E.g., `https://jira-api.example.com`. |
-| `Project key` | The short identifier for your JIRA project, all uppercase, e.g., `PROJ`. |
+| `JIRA API URL` | The base URL to the JIRA instance API. E.g., `https://jira-api.example.com`. This is optional. If not entered, the Web URL value be used. |
+| `Project key` | Put a JIRA project key (in uppercase), e.g. `MARS` in this field. This is only for testing the configuration settings. JIRA integration in GitLab works with _all_ JIRA projects in your JIRA instance. This field will be removed in a future release. |
 | `Username` | The user name created in [configuring JIRA step](#configuring-jira). |
 | `Password` |The password of the user created in [configuring JIRA step](#configuring-jira). |
 | `JIRA issue transition` | This is the ID of a transition that moves issues to a closed state. You can find this number under JIRA workflow administration ([see screenshot](img/jira_workflow_screenshot.png)). **Closing JIRA issues via commits or Merge Requests won't work if you don't set the ID correctly.** |
 
 After saving the configuration, your GitLab project will be able to interact
-with the linked JIRA project.
+with all JIRA projects in your JIRA instance.
 
 ![JIRA service page](img/jira_service_page.png)
 
diff --git a/doc/user/project/integrations/prometheus.md b/doc/user/project/integrations/prometheus.md
index d3fb5916dc64263e74a69dcbcc77ec01291f0520..3de14d456e8e96f82e0ab1d73a53b7e73ec18408 100644
--- a/doc/user/project/integrations/prometheus.md
+++ b/doc/user/project/integrations/prometheus.md
@@ -167,15 +167,15 @@ environment which has had a successful deployment.
 ## Determining the performance impact of a merge
 
 > [Introduced][ce-10408] in GitLab 9.2.
+> GitLab 9.3 added the [numeric comparison](https://gitlab.com/gitlab-org/gitlab-ce/issues/27439) of the 30 minute averages.
 
 Developers can view the performance impact of their changes within the merge
-request workflow. When a source branch has been deployed to an environment, a
-sparkline will appear showing the average memory consumption of the app. The dot
+request workflow. When a source branch has been deployed to an environment, a sparkline and numeric comparison of the average memory consumption will appear. On the sparkline, a dot
 indicates when the current changes were deployed, with up to 30 minutes of
-performance data displayed before and after. The sparkline will be updated after
+performance data displayed before and after. The comparison shows the difference between the 30 minute average before and after the deployment. This information is be updated after
 each commit has been deployed.
 
-Once merged and the target branch has been redeployed, the sparkline will switch
+Once merged and the target branch has been redeployed, the metrics will switch
 to show the new environments this revision has been deployed to.
 
 Performance data will be available for the duration it is persisted on the
diff --git a/doc/user/project/issue_board.md b/doc/user/project/issue_board.md
index 5aa8337b75db33f115046309b8f844fa49bf03f6..ebea7062ecb6c384a0e672d24f5fcaef5eaa59e7 100644
--- a/doc/user/project/issue_board.md
+++ b/doc/user/project/issue_board.md
@@ -31,10 +31,11 @@ Below is a table of the definitions used for GitLab's Issue Board.
 | **Card**        | Every card represents an issue and it is shown under the list for which it has a label. The information you can see on a card consists of the issue number, the issue title, the assignee and the labels associated with it. You can drag cards around from one list to another. You can re-order cards within a list. |
 
 There are two types of lists, the ones you create based on your labels, and
-one default:
+two defaults:
 
 - Label list: a list based on a label. It shows all opened issues with that label.
-- **Done** (default): shows all closed issues. Always appears on the very right.
+- **Backlog** (default): shows all open issues that does not belong to one of lists. Always appears on the very left.
+- **Closed** (default): shows all closed issues. Always appears on the very right.
 
 ![GitLab Issue Board](img/issue_board.png)
 
diff --git a/doc/user/project/issues/confidential_issues.md b/doc/user/project/issues/confidential_issues.md
index 1760b1821142fb1b034bc63e63757bd2b7157e25..208be7d0ed579bd47a8cc7684e7faa66a31c5072 100644
--- a/doc/user/project/issues/confidential_issues.md
+++ b/doc/user/project/issues/confidential_issues.md
@@ -43,9 +43,8 @@ next to the issues that are marked as confidential.
 
 ---
 
-Likewise, while inside the issue, you can see the eye-slash icon right next to
-the issue number, but there is also an indicator in the comment area that the
-issue you are commenting on is confidential.
+While inside the issue, you can see a persistent dark banner at the top of the
+screen.
 
 ![Confidential issue page](img/confidential_issues_issue_page.png)
 
diff --git a/doc/user/project/issues/img/confidential_issues_issue_page.png b/doc/user/project/issues/img/confidential_issues_issue_page.png
index f04ec8ff32bf49b31873f678c37933ecad1ef516..91f7cc8d3caabe9d07b4d6f57b2f5bd14c8b28f3 100755
Binary files a/doc/user/project/issues/img/confidential_issues_issue_page.png and b/doc/user/project/issues/img/confidential_issues_issue_page.png differ
diff --git a/doc/user/project/new_ci_build_permissions_model.md b/doc/user/project/new_ci_build_permissions_model.md
index e9512497d6cedb7193577bbda59779374e77754d..271adee7da110e4773c5035da7a825dfd98da71a 100644
--- a/doc/user/project/new_ci_build_permissions_model.md
+++ b/doc/user/project/new_ci_build_permissions_model.md
@@ -212,9 +212,9 @@ Container Registries for private projects.
   access token created explicitly for this purpose). This issue is resolved with
   latest changes in GitLab Runner 1.8 which receives GitLab credentials with
   build data.
-- Starting with GitLab 8.12, if you have 2FA enabled in your account, you need
-  to pass a personal access token instead of your password in order to login to
-  GitLab's Container Registry.
+- Starting from GitLab 8.12, if you have [2FA] enabled in your account, you need
+  to pass a [personal access token][pat] instead of your password in order to
+  login to GitLab's Container Registry.
 
 Your jobs can access all container images that you would normally have access
 to. The only implication is that you can push to the Container Registry of the
@@ -239,3 +239,5 @@ test:
 [update-docs]: https://gitlab.com/gitlab-org/gitlab-ce/tree/master/doc/update
 [workhorse]: https://gitlab.com/gitlab-org/gitlab-workhorse
 [jobenv]: ../../ci/variables/README.md#predefined-variables-environment-variables
+[2fa]: ../profile/account/two_factor_authentication.md
+[pat]: ../profile/personal_access_tokens.md
diff --git a/doc/user/project/pipelines/schedules.md b/doc/user/project/pipelines/schedules.md
index d19d184f9b09e7e53f34ea6f7166fda2c9be3098..17cc21238ff3917cc8c534fa493b4468b154c168 100644
--- a/doc/user/project/pipelines/schedules.md
+++ b/doc/user/project/pipelines/schedules.md
@@ -31,6 +31,26 @@ is installed on.
 
 ![Schedules list](img/pipeline_schedules_list.png)
 
+## Using only and except
+
+To configure that a job can be executed only when the pipeline has been
+scheduled (or the opposite), you can use
+[only and except](../../../ci/yaml/README.md#only-and-except) configuration keywords.
+
+```
+job:on-schedule:
+  only:
+    - schedules
+  script:
+    - make world
+
+job:
+  except:
+    - schedules
+  script:
+    - make build
+```
+
 ## Taking ownership
 
 Pipelines are executed as a user, who owns a schedule. This influences what
diff --git a/doc/workflow/shortcuts.md b/doc/workflow/shortcuts.md
index c5b7488be69eb33e35696079a56f60256d14ff18..87416008e9811603ca3e88f072785b28094e766c 100644
--- a/doc/workflow/shortcuts.md
+++ b/doc/workflow/shortcuts.md
@@ -6,7 +6,10 @@ You can see GitLab's keyboard shortcuts by using 'shift + ?'
 
 | Keyboard Shortcut | Description |
 | ----------------- | ----------- |
+| <kbd>n</kbd> | Main navigation |
 | <kbd>s</kbd> | Focus search |
+| <kbd>f</kbd> | Focus filter |
+| <kbd>p b</kbd> | Show/hide the Performance Bar |
 | <kbd>?</kbd> | Show/hide this dialog |
 | <kbd>⌘</kbd> + <kbd>shift</kbd> + <kbd>p</kbd> | Toggle markdown preview |
 | <kbd>↑</kbd> | Edit last comment (when focused on an empty textarea) |
diff --git a/features/steps/project/source/browse_files.rb b/features/steps/project/source/browse_files.rb
index d099d7af1679e433945e0605119fe8ae703ac2b2..80aa3a047a02553afae0908c1a3c0f01b6ae7e53 100644
--- a/features/steps/project/source/browse_files.rb
+++ b/features/steps/project/source/browse_files.rb
@@ -89,10 +89,7 @@ class Spinach::Features::ProjectSourceBrowseFiles < Spinach::FeatureSteps
   end
 
   step 'I fill the new branch name' do
-    first('button.js-target-branch', visible: true).click
-    find('.create-new-branch', visible: true).click
-    find('#new_branch_name', visible: true).set('new_branch_name')
-    find('.js-new-branch-btn', visible: true).click
+    fill_in :branch_name, with: 'new_branch_name', visible: true
   end
 
   step 'I fill the new file name with an illegal name' do
diff --git a/features/support/capybara.rb b/features/support/capybara.rb
index 6da8aaac6cb077e2f99a51a14aae3cceb7d7c0f1..f4691647d4b5c6608055311c63eb3801ed9735fe 100644
--- a/features/support/capybara.rb
+++ b/features/support/capybara.rb
@@ -11,8 +11,10 @@ Capybara.register_driver :poltergeist do |app|
     js_errors: true,
     timeout: timeout,
     window_size: [1366, 768],
+    url_whitelist: %w[localhost 127.0.0.1],
+    url_blacklist: %w[.mp4 .png .gif .avi .bmp .jpg .jpeg],
     phantomjs_options: [
-      '--load-images=no'
+      '--load-images=yes'
     ]
   )
 end
diff --git a/lib/banzai/reference_parser/base_parser.rb b/lib/banzai/reference_parser/base_parser.rb
index 1e2536231d84cfffe55df069d16f8b9434b47fbd..279fca8d04333cc0eca362e6b082db81601fd046 100644
--- a/lib/banzai/reference_parser/base_parser.rb
+++ b/lib/banzai/reference_parser/base_parser.rb
@@ -171,7 +171,7 @@ module Banzai
             collection.where(id: to_query).each { |row| cache[row.id] = row }
           end
 
-          cache.values_at(*ids)
+          cache.values_at(*ids).compact
         else
           collection.where(id: ids)
         end
diff --git a/lib/github/import.rb b/lib/github/import.rb
index 9c7eb965f93d2f02b8e3cb971d107f0364d33230..b20614b30602d64912eafc307d8c7beb0ea3a971 100644
--- a/lib/github/import.rb
+++ b/lib/github/import.rb
@@ -92,7 +92,7 @@ module Github
     end
 
     def fetch_wiki_repository
-      wiki_url  = "https://{options.fetch(:token)}@github.com/#{repo}.wiki.git"
+      wiki_url  = "https://#{options.fetch(:token)}@github.com/#{repo}.wiki.git"
       wiki_path = "#{project.path_with_namespace}.wiki"
 
       unless project.wiki.repository_exists?
diff --git a/lib/gitlab/ci/status/external/common.rb b/lib/gitlab/ci/status/external/common.rb
index 4969a3508628bf646aabcf8173320fde2b5e4654..9307545b5b16d40c1dc2b1f792ee1c1c29ee2162 100644
--- a/lib/gitlab/ci/status/external/common.rb
+++ b/lib/gitlab/ci/status/external/common.rb
@@ -3,6 +3,10 @@ module Gitlab
     module Status
       module External
         module Common
+          def label
+            subject.description
+          end
+
           def has_details?
             subject.target_url.present? &&
               can?(user, :read_commit_status, subject)
diff --git a/lib/gitlab/etag_caching/middleware.rb b/lib/gitlab/etag_caching/middleware.rb
index 7f884183bb1b387c7ea506086660d510f0b31d08..1d6f5bb5e1c98fa43f55698d137f3d7c0865eee1 100644
--- a/lib/gitlab/etag_caching/middleware.rb
+++ b/lib/gitlab/etag_caching/middleware.rb
@@ -7,7 +7,7 @@ module Gitlab
 
       def call(env)
         request = Rack::Request.new(env)
-        route = Gitlab::EtagCaching::Router.match(request)
+        route = Gitlab::EtagCaching::Router.match(request.path_info)
         return @app.call(env) unless route
 
         track_event(:etag_caching_middleware_used, route)
diff --git a/lib/gitlab/etag_caching/router.rb b/lib/gitlab/etag_caching/router.rb
index dccc66b39181c9f20cc8d5afa3de3a7c867a3811..75167a6b088b93c30880f8d0cefe2886f71d259b 100644
--- a/lib/gitlab/etag_caching/router.rb
+++ b/lib/gitlab/etag_caching/router.rb
@@ -53,8 +53,8 @@ module Gitlab
         )
       ].freeze
 
-      def self.match(request)
-        ROUTES.find { |route| route.regexp.match(request.path_info) }
+      def self.match(path)
+        ROUTES.find { |route| route.regexp.match(path) }
       end
     end
   end
diff --git a/lib/gitlab/etag_caching/store.rb b/lib/gitlab/etag_caching/store.rb
index 0039fc01c8f030afa30476c07eab6590ae9734a4..072fcfc65e600d6dd0ac541a3081b748f0ac8ae9 100644
--- a/lib/gitlab/etag_caching/store.rb
+++ b/lib/gitlab/etag_caching/store.rb
@@ -25,6 +25,8 @@ module Gitlab
       end
 
       def redis_key(key)
+        raise 'Invalid key' if !Rails.env.production? && !Gitlab::EtagCaching::Router.match(key)
+
         "#{REDIS_NAMESPACE}#{key}"
       end
     end
diff --git a/lib/gitlab/git/diff.rb b/lib/gitlab/git/diff.rb
index 8926aa1992566586393f3c7943856ac5638c5546..88ad760bea3094222e8dfeccc89864256dc5d562 100644
--- a/lib/gitlab/git/diff.rb
+++ b/lib/gitlab/git/diff.rb
@@ -23,6 +23,23 @@ module Gitlab
       class << self
         # The maximum size of a diff to display.
         def size_limit
+          if RequestStore.active?
+            RequestStore['gitlab_git_diff_size_limit'] ||= find_size_limit
+          else
+            find_size_limit
+          end
+        end
+
+        # The maximum size before a diff is collapsed.
+        def collapse_limit
+          if RequestStore.active?
+            RequestStore['gitlab_git_diff_collapse_limit'] ||= find_collapse_limit
+          else
+            find_collapse_limit
+          end
+        end
+
+        def find_size_limit
           if Feature.enabled?('gitlab_git_diff_size_limit_increase')
             200.kilobytes
           else
@@ -30,8 +47,7 @@ module Gitlab
           end
         end
 
-        # The maximum size before a diff is collapsed.
-        def collapse_limit
+        def find_collapse_limit
           if Feature.enabled?('gitlab_git_diff_size_limit_increase')
             100.kilobytes
           else
diff --git a/lib/gitlab/performance_bar.rb b/lib/gitlab/performance_bar.rb
new file mode 100644
index 0000000000000000000000000000000000000000..163a40ad306b9b9a06ac26c9de9aa50ed40a46e1
--- /dev/null
+++ b/lib/gitlab/performance_bar.rb
@@ -0,0 +1,7 @@
+module Gitlab
+  module PerformanceBar
+    def self.enabled?
+      Feature.enabled?('gitlab_performance_bar')
+    end
+  end
+end
diff --git a/lib/gitlab/performance_bar/peek_performance_bar_with_rack_body.rb b/lib/gitlab/performance_bar/peek_performance_bar_with_rack_body.rb
new file mode 100644
index 0000000000000000000000000000000000000000..d939a6ea18d73c28b233a650fe625198e9d4e49f
--- /dev/null
+++ b/lib/gitlab/performance_bar/peek_performance_bar_with_rack_body.rb
@@ -0,0 +1,22 @@
+# This solves a bug with a X-Senfile header that wouldn't be set properly, see
+# https://github.com/peek/peek-performance_bar/pull/27
+module Gitlab
+  module PerformanceBar
+    module PeekPerformanceBarWithRackBody
+      def call(env)
+        @env = env
+        reset_stats
+
+        @total_requests += 1
+        first_request if @total_requests == 1
+
+        env['process.request_start'] = @start.to_f
+        env['process.total_requests'] = total_requests
+
+        status, headers, body = @app.call(env)
+        body = Rack::BodyProxy.new(body) { record_request }
+        [status, headers, body]
+      end
+    end
+  end
+end
diff --git a/lib/gitlab/performance_bar/peek_query_tracker.rb b/lib/gitlab/performance_bar/peek_query_tracker.rb
new file mode 100644
index 0000000000000000000000000000000000000000..7ab80f5ee0fc46ee24195c19e5f5f18fc3746d30
--- /dev/null
+++ b/lib/gitlab/performance_bar/peek_query_tracker.rb
@@ -0,0 +1,39 @@
+# Inspired by https://github.com/peek/peek-pg/blob/master/lib/peek/views/pg.rb
+module Gitlab
+  module PerformanceBar
+    module PeekQueryTracker
+      def sorted_queries
+        PEEK_DB_CLIENT.query_details.
+          sort { |a, b| b[:duration] <=> a[:duration] }
+      end
+
+      def results
+        super.merge(queries: sorted_queries)
+      end
+
+      private
+
+      def setup_subscribers
+        super
+
+        # Reset each counter when a new request starts
+        before_request do
+          PEEK_DB_CLIENT.query_details = []
+        end
+
+        subscribe('sql.active_record') do |_, start, finish, _, data|
+          if RequestStore.active? && RequestStore.store[:peek_enabled]
+            track_query(data[:sql].strip, data[:binds], start, finish)
+          end
+        end
+      end
+
+      def track_query(raw_query, bindings, start, finish)
+        query = Gitlab::Sherlock::Query.new(raw_query, start, finish)
+        query_info = { duration: '%.3f' % query.duration, sql: query.formatted_query }
+
+        PEEK_DB_CLIENT.query_details << query_info
+      end
+    end
+  end
+end
diff --git a/lib/peek/rblineprof/custom_controller_helpers.rb b/lib/peek/rblineprof/custom_controller_helpers.rb
new file mode 100644
index 0000000000000000000000000000000000000000..99f9c2c9b049a6f817ae46136a4c5b9e117790eb
--- /dev/null
+++ b/lib/peek/rblineprof/custom_controller_helpers.rb
@@ -0,0 +1,96 @@
+module Peek
+  module Rblineprof
+    module CustomControllerHelpers
+      extend ActiveSupport::Concern
+
+      # This will become useless once https://github.com/peek/peek-rblineprof/pull/5
+      # is merged
+      def pygmentize(file_name, code, lexer = nil)
+        if lexer.present?
+          Gitlab::Highlight.highlight(file_name, code)
+        else
+          "<pre>#{Rack::Utils.escape_html(code)}</pre>"
+        end
+      end
+
+      # rubocop:disable all
+      def inject_rblineprof
+        ret = nil
+        profile = lineprof(rblineprof_profiler_regex) do
+          ret = yield
+        end
+
+        if response.content_type =~ %r|text/html|
+          sort = params[:lineprofiler_sort]
+          mode = params[:lineprofiler_mode] || 'cpu'
+          min  = (params[:lineprofiler_min] || 5).to_i * 1000
+          summary = params[:lineprofiler_summary]
+
+          # Sort each file by the longest calculated time
+          per_file = profile.map do |file, lines|
+            total, child, excl, total_cpu, child_cpu, excl_cpu = lines[0]
+
+            wall = summary == 'exclusive' ? excl : total
+            cpu  = summary == 'exclusive' ? excl_cpu : total_cpu
+            idle = summary == 'exclusive' ? (excl - excl_cpu) : (total - total_cpu)
+
+            [
+              file, lines,
+              wall, cpu, idle,
+              sort == 'idle' ? idle : sort == 'cpu' ? cpu : wall
+            ]
+          end.sort_by{ |a,b,c,d,e,f| -f }
+
+          output = ''
+          per_file.each do |file_name, lines, file_wall, file_cpu, file_idle, file_sort|
+
+            output << "<div class='peek-rblineprof-file'><div class='heading'>"
+
+            show_src = file_sort > min
+            tmpl = show_src ? "<a href='#' class='js-lineprof-file'>%s</a>" : "%s"
+
+            if mode == 'cpu'
+              output << sprintf("<span class='duration'>% 8.1fms + % 8.1fms</span> #{tmpl}", file_cpu / 1000.0, file_idle / 1000.0, file_name.sub(Rails.root.to_s + '/', ''))
+            else
+              output << sprintf("<span class='duration'>% 8.1fms</span> #{tmpl}", file_wall/1000.0, file_name.sub(Rails.root.to_s + '/', ''))
+            end
+
+            output << "</div>" # .heading
+
+            next unless show_src
+
+            output << "<div class='data'>"
+            code = []
+            times = []
+            File.readlines(file_name).each_with_index do |line, i|
+              code << line
+              wall, cpu, calls = lines[i + 1]
+
+              if calls && calls > 0
+                if mode == 'cpu'
+                  idle = wall - cpu
+                  times << sprintf("% 8.1fms + % 8.1fms (% 5d)", cpu / 1000.0, idle / 1000.0, calls)
+                else
+                  times << sprintf("% 8.1fms (% 5d)", wall / 1000.0, calls)
+                end
+              else
+                times << ' '
+              end
+            end
+            output << "<pre class='duration'>#{times.join("\n")}</pre>"
+            # The following line was changed from
+            # https://github.com/peek/peek-rblineprof/blob/8d3b7a283a27de2f40abda45974516693d882258/lib/peek/rblineprof/controller_helpers.rb#L125
+            # This will become useless once https://github.com/peek/peek-rblineprof/pull/16
+            # is merged and is implemented.
+            output << "<pre class='code highlight white'>#{pygmentize(file_name, code.join, 'ruby')}</pre>"
+            output << "</div></div>" # .data then .peek-rblineprof-file
+          end
+
+          response.body += "<div class='peek-rblineprof-modal' id='line-profile'>#{output}</div>".html_safe
+        end
+
+        ret
+      end
+    end
+  end
+end
diff --git a/scripts/static-analysis b/scripts/static-analysis
index 7dc8f6790364041d58291c05df26c943758f2ca5..6d35684b97ff10181b879fa3b2c850da7985bab3 100755
--- a/scripts/static-analysis
+++ b/scripts/static-analysis
@@ -3,7 +3,7 @@
 require ::File.expand_path('../lib/gitlab/popen', __dir__)
 
 tasks = [
-  %w[bundle exec bundle-audit check --update --ignore CVE-2016-4658],
+  %w[bundle exec bundle-audit check --update --ignore CVE-2016-4658 CVE-2017-5029],
   %w[bundle exec rake config_lint],
   %w[bundle exec rake flay],
   %w[bundle exec rake haml_lint],
diff --git a/spec/controllers/projects/branches_controller_spec.rb b/spec/controllers/projects/branches_controller_spec.rb
index f285e5333d65f010580f98f165f681f10a257973..f9e21f9d8f625d3947b855386cd47b2a9d3d0068 100644
--- a/spec/controllers/projects/branches_controller_spec.rb
+++ b/spec/controllers/projects/branches_controller_spec.rb
@@ -367,19 +367,5 @@ describe Projects::BranchesController do
         expect(parsed_response.first).to eq 'master'
       end
     end
-
-    context 'show_all = true' do
-      it 'returns all the branches name' do
-        get :index,
-            namespace_id: project.namespace,
-            project_id: project,
-            format: :json,
-            show_all: true
-
-        parsed_response = JSON.parse(response.body)
-
-        expect(parsed_response.length).to eq(project.repository.branches.count)
-      end
-    end
   end
 end
diff --git a/spec/controllers/projects/issues_controller_spec.rb b/spec/controllers/projects/issues_controller_spec.rb
index a38ae2eb99030bf92f16a8c32fb0d8b871257be6..b65e9e0dfc0ad6789a33d7866cc9b3bab7ce6e60 100644
--- a/spec/controllers/projects/issues_controller_spec.rb
+++ b/spec/controllers/projects/issues_controller_spec.rb
@@ -260,6 +260,7 @@ describe Projects::IssuesController do
             before { allow_any_instance_of(described_class).to receive(:verify_recaptcha).and_return(false) }
 
             it 'rejects an issue recognized as a spam' do
+              expect(Gitlab::Recaptcha).to receive(:load_configurations!).and_return(true)
               expect { update_spam_issue }.not_to change{ issue.reload.title }
             end
 
diff --git a/spec/controllers/projects/pipelines_controller_spec.rb b/spec/controllers/projects/pipelines_controller_spec.rb
index c880da1e36a4c428b32a84b4b8a5b9d6d598cedb..7660866be8b640810d3e0bf951055fc66f0ab1af 100644
--- a/spec/controllers/projects/pipelines_controller_spec.rb
+++ b/spec/controllers/projects/pipelines_controller_spec.rb
@@ -5,9 +5,12 @@ describe Projects::PipelinesController do
 
   let(:user) { create(:user) }
   let(:project) { create(:empty_project, :public) }
+  let(:feature) { ProjectFeature::DISABLED }
 
   before do
     project.add_developer(user)
+    project.project_feature.update(
+      builds_access_level: feature)
 
     sign_in(user)
   end
@@ -160,16 +163,26 @@ describe Projects::PipelinesController do
                    format: :json
     end
 
-    it 'retries a pipeline without returning any content' do
-      expect(response).to have_http_status(:no_content)
-      expect(build.reload).to be_retried
+    context 'when builds are enabled' do
+      let(:feature) { ProjectFeature::ENABLED }
+  
+      it 'retries a pipeline without returning any content' do
+        expect(response).to have_http_status(:no_content)
+        expect(build.reload).to be_retried
+      end
+    end
+
+    context 'when builds are disabled' do
+      it 'fails to retry pipeline' do
+        expect(response).to have_http_status(:not_found)
+      end
     end
   end
 
   describe 'POST cancel.json' do
     let!(:pipeline) { create(:ci_pipeline, project: project) }
     let!(:build) { create(:ci_build, :running, pipeline: pipeline) }
-
+  
     before do
       post :cancel, namespace_id: project.namespace,
                     project_id: project,
@@ -177,9 +190,19 @@ describe Projects::PipelinesController do
                     format: :json
     end
 
-    it 'cancels a pipeline without returning any content' do
-      expect(response).to have_http_status(:no_content)
-      expect(pipeline.reload).to be_canceled
+    context 'when builds are enabled' do
+      let(:feature) { ProjectFeature::ENABLED }
+  
+      it 'cancels a pipeline without returning any content' do
+        expect(response).to have_http_status(:no_content)
+        expect(pipeline.reload).to be_canceled
+      end
+    end
+
+    context 'when builds are disabled' do
+      it 'fails to retry pipeline' do
+        expect(response).to have_http_status(:not_found)
+      end
     end
   end
 end
diff --git a/spec/features/help_pages_spec.rb b/spec/features/help_pages_spec.rb
index e0b2404e60ae8afc166087d087ca3d137d3a1e23..31014f5cad260906cfc1e1dd86e54c6f7f6b1b05 100644
--- a/spec/features/help_pages_spec.rb
+++ b/spec/features/help_pages_spec.rb
@@ -34,7 +34,7 @@ describe 'Help Pages', feature: true do
     end
   end
 
-  context 'in a production environment with version check enabled', js: true do
+  context 'in a production environment with version check enabled', :js do
     before do
       allow(Rails.env).to receive(:production?) { true }
       allow(current_application_settings).to receive(:version_check_enabled) { true }
@@ -44,18 +44,12 @@ describe 'Help Pages', feature: true do
       visit help_path
     end
 
-    it 'should display a version check image' do
-      expect(find('.js-version-status-badge')).to be_visible
+    it 'has a version check image' do
+      expect(find('.js-version-status-badge', visible: false)['src']).to end_with('/version-check-url')
     end
 
-    it 'should have a src url' do
-      expect(find('.js-version-status-badge')['src']).to match(/\/version-check-url/)
-    end
-
-    it 'should hide the version check image if the image request fails' do
-      # We use '--load-images=no' with poltergeist so we must trigger manually
-      execute_script("$('.js-version-status-badge').trigger('error');")
-
+    it 'hides the version check image if the image request fails' do
+      # We use '--load-images=yes' with poltergeist so the image fails to load
       expect(find('.js-version-status-badge', visible: false)).not_to be_visible
     end
   end
diff --git a/spec/features/projects/artifacts/file_spec.rb b/spec/features/projects/artifacts/file_spec.rb
index 25c4f3c87a2a0487f234943082b8d8cf9ba39779..860373e531bf5a44094d3a3c0ce7f4d0cbbd60b6 100644
--- a/spec/features/projects/artifacts/file_spec.rb
+++ b/spec/features/projects/artifacts/file_spec.rb
@@ -39,6 +39,7 @@ feature 'Artifact file', :js, feature: true do
 
   context 'JPG file' do
     before do
+      page.driver.browser.url_blacklist = []
       visit_file('rails_sample.jpg')
 
       wait_for_requests
diff --git a/spec/features/projects/blobs/edit_spec.rb b/spec/features/projects/blobs/edit_spec.rb
index 1a38997450db94732fba41e795bf42edc8725ef8..d04c3248ead15c8e46dd068f62e9fe25fd6097f8 100644
--- a/spec/features/projects/blobs/edit_spec.rb
+++ b/spec/features/projects/blobs/edit_spec.rb
@@ -102,7 +102,7 @@ feature 'Editing file blob', feature: true, js: true do
 
         it 'shows blob editor with same branch' do
           expect(page).to have_current_path(namespace_project_edit_blob_path(project.namespace, project, tree_join(branch, file_path)))
-          expect(find('.js-target-branch .dropdown-toggle-text').text).to eq(branch)
+          expect(find('.js-branch-name').value).to eq(branch)
         end
       end
 
@@ -112,7 +112,7 @@ feature 'Editing file blob', feature: true, js: true do
         end
 
         it 'shows blob editor with patch branch' do
-          expect(find('.js-target-branch .dropdown-toggle-text').text).to eq('patch-1')
+          expect(find('.js-branch-name').value).to eq('patch-1')
         end
       end
     end
@@ -128,7 +128,7 @@ feature 'Editing file blob', feature: true, js: true do
 
       it 'shows blob editor with same branch' do
         expect(page).to have_current_path(namespace_project_edit_blob_path(project.namespace, project, tree_join(branch, file_path)))
-        expect(find('.js-target-branch .dropdown-toggle-text').text).to eq(branch)
+        expect(find('.js-branch-name').value).to eq(branch)
       end
     end
   end
diff --git a/spec/features/projects/blobs/user_create_spec.rb b/spec/features/projects/blobs/user_create_spec.rb
deleted file mode 100644
index 4b6c55f5f44fbb68bebafbf615a28ba12001b6d7..0000000000000000000000000000000000000000
--- a/spec/features/projects/blobs/user_create_spec.rb
+++ /dev/null
@@ -1,94 +0,0 @@
-require 'spec_helper'
-
-feature 'New blob creation', feature: true, js: true do
-  include TargetBranchHelpers
-
-  given(:user) { create(:user) }
-  given(:role) { :developer }
-  given(:project) { create(:project) }
-  given(:content) { 'class NextFeature\nend\n' }
-
-  background do
-    login_as(user)
-    project.team << [user, role]
-    visit namespace_project_new_blob_path(project.namespace, project, 'master')
-  end
-
-  def edit_file
-    wait_for_requests
-    fill_in 'file_name', with: 'feature.rb'
-    execute_script("ace.edit('editor').setValue('#{content}')")
-  end
-
-  def commit_file
-    click_button 'Commit changes'
-  end
-
-  context 'with default target branch' do
-    background do
-      edit_file
-      commit_file
-    end
-
-    scenario 'creates the blob in the default branch' do
-      expect(page).to have_content 'master'
-      expect(page).to have_content 'successfully created'
-      expect(page).to have_content 'NextFeature'
-    end
-  end
-
-  context 'with different target branch' do
-    background do
-      edit_file
-      select_branch('feature')
-      commit_file
-    end
-
-    scenario 'creates the blob in the different branch' do
-      expect(page).to have_content 'feature'
-      expect(page).to have_content 'successfully created'
-    end
-  end
-
-  context 'with a new target branch' do
-    given(:new_branch_name) { 'new-feature' }
-
-    background do
-      edit_file
-      create_new_branch(new_branch_name)
-      commit_file
-    end
-
-    scenario 'creates the blob in the new branch' do
-      expect(page).to have_content new_branch_name
-      expect(page).to have_content 'successfully created'
-    end
-    scenario 'returns you to the mr' do
-      expect(page).to have_content 'New Merge Request'
-      expect(page).to have_content "From #{new_branch_name} into master"
-      expect(page).to have_content 'Add new file'
-    end
-  end
-
-  context 'the file already exist in the source branch' do
-    background do
-      Files::CreateService.new(
-        project,
-        user,
-        start_branch: 'master',
-        branch_name: 'master',
-        commit_message: 'Create file',
-        file_path: 'feature.rb',
-        file_content: content
-      ).execute
-      edit_file
-      commit_file
-    end
-
-    scenario 'shows error message' do
-      expect(page).to have_content('A file with this name already exists')
-      expect(page).to have_content('New file')
-      expect(page).to have_content('NextFeature')
-    end
-  end
-end
diff --git a/spec/features/projects/features_visibility_spec.rb b/spec/features/projects/features_visibility_spec.rb
index c49648f54bdcab621a28870fc91602ae9454c6d7..d76b5e4ef1b0dbb6d5d3535e218169a5c1767cb2 100644
--- a/spec/features/projects/features_visibility_spec.rb
+++ b/spec/features/projects/features_visibility_spec.rb
@@ -68,9 +68,12 @@ describe 'Edit Project Settings', feature: true do
   end
 
   describe 'project features visibility pages' do
+    let(:pipeline) { create(:ci_empty_pipeline, project: project) }
+    let(:job) { create(:ci_build, pipeline: pipeline) }
+
     let(:tools) do
       {
-        builds: namespace_project_pipelines_path(project.namespace, project),
+        builds: namespace_project_job_path(project.namespace, project, job),
         issues: namespace_project_issues_path(project.namespace, project),
         wiki: namespace_project_wiki_path(project.namespace, project, :home),
         snippets: namespace_project_snippets_path(project.namespace, project),
diff --git a/spec/features/projects/user_create_dir_spec.rb b/spec/features/projects/user_create_dir_spec.rb
index 5dfdc465d7d1770824be84d9d4b94d35f2ad5006..aeb7e0b7c33aa13ee7f4f1984d23c53604c0e23c 100644
--- a/spec/features/projects/user_create_dir_spec.rb
+++ b/spec/features/projects/user_create_dir_spec.rb
@@ -1,8 +1,6 @@
 require 'spec_helper'
 
 feature 'New directory creation', feature: true, js: true do
-  include TargetBranchHelpers
-
   given(:user) { create(:user) }
   given(:role) { :developer }
   given(:project) { create(:project) }
@@ -36,23 +34,11 @@ feature 'New directory creation', feature: true, js: true do
     end
   end
 
-  context 'with different target branch' do
-    background do
-      select_branch('feature')
-      create_directory
-    end
-
-    scenario 'creates the directory in the different branch' do
-      expect(page).to have_content 'feature'
-      expect(page).to have_content 'The directory has been successfully created'
-    end
-  end
-
   context 'with a new target branch' do
     given(:new_branch_name) { 'new-feature' }
 
     background do
-      create_new_branch(new_branch_name)
+      fill_in :branch_name, with: new_branch_name
       create_directory
     end
 
diff --git a/spec/features/user_can_display_performance_bar_spec.rb b/spec/features/user_can_display_performance_bar_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..c2842255b861d2b538361292931d85b3933b8152
--- /dev/null
+++ b/spec/features/user_can_display_performance_bar_spec.rb
@@ -0,0 +1,81 @@
+require 'rails_helper'
+
+describe 'User can display performacne bar', :js do
+  shared_examples 'performance bar is disabled' do
+    it 'does not show the performance bar by default' do
+      expect(page).not_to have_css('#peek')
+    end
+
+    context 'when user press `pb`' do
+      before do
+        find('body').native.send_keys('pb')
+      end
+
+      it 'does not show the performance bar by default' do
+        expect(page).not_to have_css('#peek')
+      end
+    end
+  end
+
+  shared_examples 'performance bar is enabled' do
+    it 'does not show the performance bar by default' do
+      expect(page).not_to have_css('#peek')
+    end
+
+    context 'when user press `pb`' do
+      before do
+        find('body').native.send_keys('pb')
+      end
+
+      it 'does not show the performance bar by default' do
+        expect(page).not_to have_css('#peek')
+      end
+    end
+  end
+
+  context 'when user is logged-out' do
+    before do
+      visit root_path
+    end
+
+    context 'when the gitlab_performance_bar feature is disabled' do
+      before do
+        Feature.disable('gitlab_performance_bar')
+      end
+
+      it_behaves_like 'performance bar is disabled'
+    end
+
+    context 'when the gitlab_performance_bar feature is enabled' do
+      before do
+        Feature.enable('gitlab_performance_bar')
+      end
+
+      it_behaves_like 'performance bar is disabled'
+    end
+  end
+
+  context 'when user is logged-in' do
+    before do
+      login_as :user
+
+      visit root_path
+    end
+
+    context 'when the gitlab_performance_bar feature is disabled' do
+      before do
+        Feature.disable('gitlab_performance_bar')
+      end
+
+      it_behaves_like 'performance bar is disabled'
+    end
+
+    context 'when the gitlab_performance_bar feature is enabled' do
+      before do
+        Feature.enable('gitlab_performance_bar')
+      end
+
+      it_behaves_like 'performance bar is enabled'
+    end
+  end
+end
diff --git a/spec/helpers/projects_helper_spec.rb b/spec/helpers/projects_helper_spec.rb
index a695621b87a2499a58aa40d169925668aa34c685..3ed0b0a756bd6fa40c370d8ca4926a47648d6ae3 100644
--- a/spec/helpers/projects_helper_spec.rb
+++ b/spec/helpers/projects_helper_spec.rb
@@ -300,4 +300,37 @@ describe ProjectsHelper do
       expect(helper.send(:visibility_select_options, project, Gitlab::VisibilityLevel::PRIVATE)).to include('Private')
     end
   end
+
+  describe '#get_project_nav_tabs' do
+    let(:project) { create(:empty_project) }
+    let(:user)    { create(:user) }
+
+    before do
+      allow(helper).to receive(:can?) { true }
+    end
+
+    subject do
+      helper.send(:get_project_nav_tabs, project, user)
+    end
+
+    context 'when builds feature is enabled' do
+      before do
+        allow(project).to receive(:builds_enabled?).and_return(true)
+      end
+
+      it "does include pipelines tab" do
+        is_expected.to include(:pipelines)
+      end
+    end
+
+    context 'when builds feature is disabled' do
+      before do
+        allow(project).to receive(:builds_enabled?).and_return(false)
+      end
+
+      it "do not include pipelines tab" do
+        is_expected.not_to include(:pipelines)
+      end
+    end
+  end
 end
diff --git a/spec/initializers/8_metrics_spec.rb b/spec/initializers/8_metrics_spec.rb
index 570754621f397b75e64d06332ed27858cbef9934..a507d7f7f2b376988b18afd2e00c4c8d7650189d 100644
--- a/spec/initializers/8_metrics_spec.rb
+++ b/spec/initializers/8_metrics_spec.rb
@@ -7,6 +7,7 @@ describe 'instrument_classes', lib: true do
   before do
     allow(config).to receive(:instrument_method)
     allow(config).to receive(:instrument_methods)
+    allow(config).to receive(:instrument_instance_method)
     allow(config).to receive(:instrument_instance_methods)
   end
 
diff --git a/spec/javascripts/blob/create_branch_dropdown_spec.js b/spec/javascripts/blob/create_branch_dropdown_spec.js
deleted file mode 100644
index 6dbaa47c54404df23cbaae8a3f930f92f3a88c08..0000000000000000000000000000000000000000
--- a/spec/javascripts/blob/create_branch_dropdown_spec.js
+++ /dev/null
@@ -1,106 +0,0 @@
-import '~/gl_dropdown';
-import '~/blob/create_branch_dropdown';
-import '~/blob/target_branch_dropdown';
-
-describe('CreateBranchDropdown', () => {
-  const fixtureTemplate = 'static/target_branch_dropdown.html.raw';
-  // selectors
-  const createBranchSel = '.js-new-branch-btn';
-  const backBtnSel = '.dropdown-menu-back';
-  const cancelBtnSel = '.js-cancel-branch-btn';
-  const branchNameSel = '#new_branch_name';
-  const branchName = 'new_name';
-  let dropdown;
-
-  function createDropdown() {
-    const dropdownEl = document.querySelector('.js-project-branches-dropdown');
-    const projectBranches = getJSONFixture('project_branches.json');
-    dropdown = new gl.TargetBranchDropDown(dropdownEl);
-    dropdown.cachedRefs = projectBranches;
-    return dropdown;
-  }
-
-  function createBranchBtn() {
-    return document.querySelector(createBranchSel);
-  }
-
-  function backBtn() {
-    return document.querySelector(backBtnSel);
-  }
-
-  function cancelBtn() {
-    return document.querySelector(cancelBtnSel);
-  }
-
-  function branchNameEl() {
-    return document.querySelector(branchNameSel);
-  }
-
-  function changeBranchName(text) {
-    branchNameEl().value = text;
-    branchNameEl().dispatchEvent(new Event('change'));
-  }
-
-  preloadFixtures(fixtureTemplate);
-
-  beforeEach(() => {
-    loadFixtures(fixtureTemplate);
-    createDropdown();
-  });
-
-  it('disable submit when branch name is empty', () => {
-    expect(createBranchBtn()).toBeDisabled();
-  });
-
-  it('enable submit when branch name is present', () => {
-    changeBranchName(branchName);
-
-    expect(createBranchBtn()).not.toBeDisabled();
-  });
-
-  it('resets the form when cancel btn is clicked and triggers dropdownback', () => {
-    const spyBackEvent = spyOnEvent(backBtnSel, 'click');
-    changeBranchName(branchName);
-
-    cancelBtn().click();
-
-    expect(branchNameEl()).toHaveValue('');
-    expect(spyBackEvent).toHaveBeenTriggered();
-  });
-
-  it('resets the form when back btn is clicked', () => {
-    changeBranchName(branchName);
-
-    backBtn().click();
-
-    expect(branchNameEl()).toHaveValue('');
-  });
-
-  describe('new branch creation', () => {
-    beforeEach(() => {
-      changeBranchName(branchName);
-    });
-    it('sets the new branch name and updates the dropdown', () => {
-      spyOn(dropdown, 'setNewBranch');
-
-      createBranchBtn().click();
-
-      expect(dropdown.setNewBranch).toHaveBeenCalledWith(branchName);
-    });
-
-    it('resets the form', () => {
-      createBranchBtn().click();
-
-      expect(branchNameEl()).toHaveValue('');
-    });
-
-    it('is triggered with enter keypress', () => {
-      spyOn(dropdown, 'setNewBranch');
-      const enterEvent = new Event('keydown');
-      enterEvent.which = 13;
-      branchNameEl().dispatchEvent(enterEvent);
-
-      expect(dropdown.setNewBranch).toHaveBeenCalledWith(branchName);
-    });
-  });
-});
diff --git a/spec/javascripts/blob/target_branch_dropdown_spec.js b/spec/javascripts/blob/target_branch_dropdown_spec.js
deleted file mode 100644
index 99c9537d2eca181d042b1c502a09ff7cf48227bb..0000000000000000000000000000000000000000
--- a/spec/javascripts/blob/target_branch_dropdown_spec.js
+++ /dev/null
@@ -1,118 +0,0 @@
-import '~/gl_dropdown';
-import '~/blob/create_branch_dropdown';
-import '~/blob/target_branch_dropdown';
-
-describe('TargetBranchDropdown', () => {
-  const fixtureTemplate = 'static/target_branch_dropdown.html.raw';
-  let dropdown;
-
-  function createDropdown() {
-    const projectBranches = getJSONFixture('project_branches.json');
-    const dropdownEl = document.querySelector('.js-project-branches-dropdown');
-    dropdown = new gl.TargetBranchDropDown(dropdownEl);
-    dropdown.cachedRefs = projectBranches;
-    dropdown.refreshData();
-    return dropdown;
-  }
-
-  function submitBtn() {
-    return document.querySelector('button[type="submit"]');
-  }
-
-  function searchField() {
-    return document.querySelector('.dropdown-page-one .dropdown-input-field');
-  }
-
-  function element() {
-    return document.querySelectorAll('div.dropdown-content li a');
-  }
-
-  function elementAtIndex(index) {
-    return element()[index];
-  }
-
-  function clickElementAtIndex(index) {
-    elementAtIndex(index).click();
-  }
-
-  preloadFixtures(fixtureTemplate);
-
-  beforeEach(() => {
-    loadFixtures(fixtureTemplate);
-    createDropdown();
-  });
-
-  it('disable submit when branch is not selected', () => {
-    document.querySelector('input[name="target_branch"]').value = null;
-    clickElementAtIndex(1);
-
-    expect(submitBtn().getAttribute('disabled')).toEqual('');
-  });
-
-  it('enable submit when a branch is selected', () => {
-    clickElementAtIndex(1);
-
-    expect(submitBtn().getAttribute('disabled')).toBe(null);
-  });
-
-  it('triggers change.branch event on a branch click', () => {
-    spyOnEvent(dropdown.$dropdown, 'change.branch');
-    clickElementAtIndex(0);
-
-    expect('change.branch').toHaveBeenTriggeredOn(dropdown.$dropdown);
-  });
-
-  describe('dropdownData', () => {
-    it('cache the refs', () => {
-      const refs = dropdown.cachedRefs;
-      dropdown.cachedRefs = null;
-
-      dropdown.dropdownData(refs);
-
-      expect(dropdown.cachedRefs).toEqual(refs);
-    });
-
-    it('returns the Branches with the newBranch and defaultBranch', () => {
-      const refs = dropdown.cachedRefs;
-      dropdown.branchInput.value = 'master';
-      dropdown.newBranch = { id: 'new_branch', text: 'new_branch', title: 'new_branch' };
-
-      const branches = dropdown.dropdownData(refs).Branches;
-
-      expect(branches.length).toEqual(4);
-      expect(branches[0]).toEqual(dropdown.newBranch);
-      expect(branches[1]).toEqual({ id: 'master', text: 'master', title: 'master' });
-      expect(branches[2]).toEqual({ id: 'development', text: 'development', title: 'development' });
-      expect(branches[3]).toEqual({ id: 'staging', text: 'staging', title: 'staging' });
-    });
-  });
-
-  describe('setNewBranch', () => {
-    it('adds the new branch and select it', () => {
-      const branchName = 'new_branch';
-
-      dropdown.setNewBranch(branchName);
-
-      expect(elementAtIndex(0)).toHaveClass('is-active');
-      expect(elementAtIndex(0)).toContainHtml(branchName);
-    });
-
-    it("doesn't add a new branch if already exists in the list", () => {
-      const branchName = elementAtIndex(0).text;
-      const initialLength = element().length;
-
-      dropdown.setNewBranch(branchName);
-
-      expect(element().length).toEqual(initialLength);
-    });
-
-    it('clears the search filter', () => {
-      const branchName = elementAtIndex(0).text;
-      searchField().value = 'searching';
-
-      dropdown.setNewBranch(branchName);
-
-      expect(searchField().value).toEqual('');
-    });
-  });
-});
diff --git a/spec/javascripts/fixtures/project_branches.json b/spec/javascripts/fixtures/project_branches.json
deleted file mode 100644
index a96a4c0c09529dce3023c46a5dc6e436e34cdb0e..0000000000000000000000000000000000000000
--- a/spec/javascripts/fixtures/project_branches.json
+++ /dev/null
@@ -1,5 +0,0 @@
-[
-  "master",
-  "development",
-  "staging"
-]
diff --git a/spec/javascripts/fixtures/target_branch_dropdown.html.haml b/spec/javascripts/fixtures/target_branch_dropdown.html.haml
deleted file mode 100644
index 821fb7940a0e4d252d14ad64489d88add8452ab2..0000000000000000000000000000000000000000
--- a/spec/javascripts/fixtures/target_branch_dropdown.html.haml
+++ /dev/null
@@ -1,28 +0,0 @@
-%form.js-edit-blob-form
-  %input{type: 'hidden', name: 'target_branch', value: 'master'}
-  %div
-    .dropdown
-      %button.dropdown-menu-toggle.js-project-branches-dropdown.js-target-branch{type: 'button', data: {toggle: 'dropdown', selected: 'master', field_name: 'target_branch', form_id: '.js-edit-blob-form'}}
-      .dropdown-menu.dropdown-menu-selectable.dropdown-menu-paging
-        .dropdown-page-one
-          .dropdown-title 'Select branch'
-          .dropdown-input
-            %input.dropdown-input-field{type: 'search', value: ''}
-            %i.fa.fa-search.dropdown-input-search
-            %i.fa.fa-times-dropdown-input-clear.js-dropdown-input-clear{role: 'button'}
-          .dropdown-content
-          .dropdown-footer
-            %ul.dropdown-footer-list
-              %li
-                %a.create-new-branch.dropdown-toggle-page{href: "#"}
-                  Create new branch
-        .dropdown-page-two.dropdown-new-branch
-          %button.dropdown-title-button.dropdown-menu-back{type: 'button'}
-          .dropdown_title 'Create new branch'
-          .dropdown_content
-            %input#new_branch_name.default-dropdown-input{ type: "text", placeholder: "Name new branch" }
-              %button.btn.btn-primary.pull-left.js-new-branch-btn{ type: "button" }
-                Create
-              %button.btn.btn-default.pull-right.js-cancel-branch-btn{ type: "button" }
-                Cancel
-  %button{type: 'submit'}
diff --git a/spec/javascripts/notes_spec.js b/spec/javascripts/notes_spec.js
index bfd8b8648a626f699a18a17a3368696b19ed70ca..c6f218e4dacf697542882d9ec8f1a5f807e1c5fa 100644
--- a/spec/javascripts/notes_spec.js
+++ b/spec/javascripts/notes_spec.js
@@ -126,6 +126,7 @@ import '~/notes';
         const deferred = $.Deferred();
         spyOn($, 'ajax').and.returnValue(deferred.promise());
         spyOn(this.notes, 'revertNoteEditForm');
+        spyOn(this.notes, 'setupNewNote');
 
         $('.js-comment-button').click();
         deferred.resolve(noteEntity);
@@ -136,6 +137,46 @@ import '~/notes';
         this.notes.updateNote(updatedNote, $targetNote);
 
         expect(this.notes.revertNoteEditForm).toHaveBeenCalledWith($targetNote);
+        expect(this.notes.setupNewNote).toHaveBeenCalled();
+      });
+    });
+
+    describe('updateNoteTargetSelector', () => {
+      const hash = 'note_foo';
+      let $note;
+
+      beforeEach(() => {
+        $note = $(`<div id="${hash}"></div>`);
+        spyOn($note, 'filter').and.callThrough();
+        spyOn($note, 'toggleClass').and.callThrough();
+      });
+
+      it('sets target when hash matches', () => {
+        spyOn(gl.utils, 'getLocationHash');
+        gl.utils.getLocationHash.and.returnValue(hash);
+
+        Notes.updateNoteTargetSelector($note);
+
+        expect($note.filter).toHaveBeenCalledWith(`#${hash}`);
+        expect($note.toggleClass).toHaveBeenCalledWith('target', true);
+      });
+
+      it('unsets target when hash does not match', () => {
+        spyOn(gl.utils, 'getLocationHash');
+        gl.utils.getLocationHash.and.returnValue('note_doesnotexist');
+
+        Notes.updateNoteTargetSelector($note);
+
+        expect($note.toggleClass).toHaveBeenCalledWith('target', false);
+      });
+
+      it('unsets target when there is not a hash fragment anymore', () => {
+        spyOn(gl.utils, 'getLocationHash');
+        gl.utils.getLocationHash.and.returnValue(null);
+
+        Notes.updateNoteTargetSelector($note);
+
+        expect($note.toggleClass).toHaveBeenCalledWith('target', null);
       });
     });
 
@@ -189,9 +230,13 @@ import '~/notes';
           Notes.isUpdatedNote.and.returnValue(true);
           const $note = $('<div>');
           $notesList.find.and.returnValue($note);
+          const $newNote = $(note.html);
+          Notes.animateUpdateNote.and.returnValue($newNote);
+
           Notes.prototype.renderNote.call(notes, note, null, $notesList);
 
           expect(Notes.animateUpdateNote).toHaveBeenCalledWith(note.html, $note);
+          expect(notes.setupNewNote).toHaveBeenCalledWith($newNote);
         });
 
         describe('while editing', () => {
@@ -378,6 +423,23 @@ import '~/notes';
       });
     });
 
+    describe('putEditFormInPlace', () => {
+      it('should call gl.GLForm with GFM parameter passed through', () => {
+        spyOn(gl, 'GLForm');
+
+        const $el = jasmine.createSpyObj('$form', ['find', 'closest']);
+        $el.find.and.returnValue($('<div>'));
+        $el.closest.and.returnValue($('<div>'));
+
+        Notes.prototype.putEditFormInPlace.call({
+          getEditFormSelector: () => '',
+          enableGFM: true
+        }, $el);
+
+        expect(gl.GLForm).toHaveBeenCalledWith(jasmine.any(Object), true);
+      });
+    });
+
     describe('postComment & updateComment', () => {
       const sampleComment = 'foo';
       const updatedComment = 'bar';
diff --git a/spec/javascripts/vue_shared/components/time_ago_tooltip_spec.js b/spec/javascripts/vue_shared/components/time_ago_tooltip_spec.js
index bf28019ef241a27342409254d6451c5f5da2cc02..f3b4adc0b702f79e6465fcdcd783c5e4ce715a32 100644
--- a/spec/javascripts/vue_shared/components/time_ago_tooltip_spec.js
+++ b/spec/javascripts/vue_shared/components/time_ago_tooltip_spec.js
@@ -22,7 +22,7 @@ describe('Time ago with tooltip component', () => {
     }).$mount();
 
     expect(vm.$el.tagName).toEqual('TIME');
-    expect(vm.$el.classList.contains('js-timeago')).toEqual(true);
+    expect(vm.$el.classList.contains('js-vue-timeago')).toEqual(true);
     expect(
       vm.$el.getAttribute('data-original-title'),
     ).toEqual(gl.utils.formatDate('2017-05-08T14:57:39.781Z'));
@@ -44,17 +44,6 @@ describe('Time ago with tooltip component', () => {
     expect(vm.$el.getAttribute('data-placement')).toEqual('bottom');
   });
 
-  it('should render short format class', () => {
-    vm = new TimeagoTooltip({
-      propsData: {
-        time: '2017-05-08T14:57:39.781Z',
-        shortFormat: true,
-      },
-    }).$mount();
-
-    expect(vm.$el.classList.contains('js-short-timeago')).toEqual(true);
-  });
-
   it('should render provided html class', () => {
     vm = new TimeagoTooltip({
       propsData: {
diff --git a/spec/lib/banzai/reference_parser/base_parser_spec.rb b/spec/lib/banzai/reference_parser/base_parser_spec.rb
index f4f42bfc3ed0524b1985b260bf27313834e64247..76fab93821aa5cfa194a982ffc02fe1e7c18b302 100644
--- a/spec/lib/banzai/reference_parser/base_parser_spec.rb
+++ b/spec/lib/banzai/reference_parser/base_parser_spec.rb
@@ -114,7 +114,7 @@ describe Banzai::ReferenceParser::BaseParser, lib: true do
       expect(hash).to eq({ link => user })
     end
 
-    it 'returns an empty Hash when entry does not exist in the database' do
+    it 'returns an empty Hash when entry does not exist in the database', :request_store do
       link = double(:link)
 
       expect(link).to receive(:has_attribute?).
diff --git a/spec/lib/gitlab/ci/status/external/common_spec.rb b/spec/lib/gitlab/ci/status/external/common_spec.rb
index 5a97d98b55f674db8cd23da51ddffcb4846fe4fd..e58f5d8d4df2a11720915f2bcda7a617816a6c69 100644
--- a/spec/lib/gitlab/ci/status/external/common_spec.rb
+++ b/spec/lib/gitlab/ci/status/external/common_spec.rb
@@ -4,9 +4,10 @@ describe Gitlab::Ci::Status::External::Common do
   let(:user) { create(:user) }
   let(:project) { external_status.project }
   let(:external_target_url) { 'http://example.gitlab.com/status' }
+  let(:external_description) { 'my description' }
 
   let(:external_status) do
-    create(:generic_commit_status, target_url: external_target_url)
+    create(:generic_commit_status, target_url: external_target_url, description: external_description)
   end
 
   subject do
@@ -15,6 +16,12 @@ describe Gitlab::Ci::Status::External::Common do
       .extend(described_class)
   end
 
+  describe '#label' do
+    it 'returns description' do
+      expect(subject.label).to eq external_description
+    end
+  end
+
   describe '#has_action?' do
     it { is_expected.not_to have_action }
   end
diff --git a/spec/lib/gitlab/etag_caching/middleware_spec.rb b/spec/lib/gitlab/etag_caching/middleware_spec.rb
index 3c6ef7c7ccb29527f80f8c92a1cdc317e32cb115..4acf4f047f15a477fcef4a336191257037a48872 100644
--- a/spec/lib/gitlab/etag_caching/middleware_spec.rb
+++ b/spec/lib/gitlab/etag_caching/middleware_spec.rb
@@ -15,13 +15,13 @@ describe Gitlab::EtagCaching::Middleware do
     end
 
     it 'does not add ETag header' do
-      _, headers, _ = middleware.call(build_env(path, if_none_match))
+      _, headers, _ = middleware.call(build_request(path, if_none_match))
 
       expect(headers['ETag']).to be_nil
     end
 
     it 'passes status code from app' do
-      status, _, _ = middleware.call(build_env(path, if_none_match))
+      status, _, _ = middleware.call(build_request(path, if_none_match))
 
       expect(status).to eq app_status_code
     end
@@ -39,7 +39,7 @@ describe Gitlab::EtagCaching::Middleware do
       expect_any_instance_of(Gitlab::EtagCaching::Store)
         .to receive(:touch).and_return('123')
 
-      middleware.call(build_env(path, if_none_match))
+      middleware.call(build_request(path, if_none_match))
     end
 
     context 'when If-None-Match header was specified' do
@@ -51,7 +51,7 @@ describe Gitlab::EtagCaching::Middleware do
         expect(Gitlab::Metrics).to receive(:add_event)
           .with(:etag_caching_key_not_found, endpoint: 'issue_notes')
 
-        middleware.call(build_env(path, if_none_match))
+        middleware.call(build_request(path, if_none_match))
       end
     end
   end
@@ -65,7 +65,7 @@ describe Gitlab::EtagCaching::Middleware do
     end
 
     it 'returns this value as header' do
-      _, headers, _ = middleware.call(build_env(path, if_none_match))
+      _, headers, _ = middleware.call(build_request(path, if_none_match))
 
       expect(headers['ETag']).to eq 'W/"123"'
     end
@@ -82,17 +82,17 @@ describe Gitlab::EtagCaching::Middleware do
     it 'does not call app' do
       expect(app).not_to receive(:call)
 
-      middleware.call(build_env(path, if_none_match))
+      middleware.call(build_request(path, if_none_match))
     end
 
     it 'returns status code 304' do
-      status, _, _ = middleware.call(build_env(path, if_none_match))
+      status, _, _ = middleware.call(build_request(path, if_none_match))
 
       expect(status).to eq 304
     end
 
     it 'returns empty body' do
-      _, _, body = middleware.call(build_env(path, if_none_match))
+      _, _, body = middleware.call(build_request(path, if_none_match))
 
       expect(body).to be_empty
     end
@@ -103,7 +103,7 @@ describe Gitlab::EtagCaching::Middleware do
       expect(Gitlab::Metrics).to receive(:add_event)
         .with(:etag_caching_cache_hit, endpoint: 'issue_notes')
 
-      middleware.call(build_env(path, if_none_match))
+      middleware.call(build_request(path, if_none_match))
     end
 
     context 'when polling is disabled' do
@@ -113,7 +113,7 @@ describe Gitlab::EtagCaching::Middleware do
       end
 
       it 'returns status code 429' do
-        status, _, _ = middleware.call(build_env(path, if_none_match))
+        status, _, _ = middleware.call(build_request(path, if_none_match))
 
         expect(status).to eq 429
       end
@@ -131,7 +131,7 @@ describe Gitlab::EtagCaching::Middleware do
     it 'calls app' do
       expect(app).to receive(:call).and_return([app_status_code, {}, ['body']])
 
-      middleware.call(build_env(path, if_none_match))
+      middleware.call(build_request(path, if_none_match))
     end
 
     it 'tracks "etag_caching_resource_changed" event' do
@@ -142,7 +142,7 @@ describe Gitlab::EtagCaching::Middleware do
       expect(Gitlab::Metrics).to receive(:add_event)
         .with(:etag_caching_resource_changed, endpoint: 'issue_notes')
 
-      middleware.call(build_env(path, if_none_match))
+      middleware.call(build_request(path, if_none_match))
     end
   end
 
@@ -160,7 +160,7 @@ describe Gitlab::EtagCaching::Middleware do
       expect(Gitlab::Metrics).to receive(:add_event)
         .with(:etag_caching_header_missing, endpoint: 'issue_notes')
 
-      middleware.call(build_env(path, if_none_match))
+      middleware.call(build_request(path, if_none_match))
     end
   end
 
@@ -192,10 +192,7 @@ describe Gitlab::EtagCaching::Middleware do
       .to receive(:get).and_return(value)
   end
 
-  def build_env(path, if_none_match)
-    {
-      'PATH_INFO' => path,
-      'HTTP_IF_NONE_MATCH' => if_none_match
-    }
+  def build_request(path, if_none_match)
+    { 'PATH_INFO' => path, 'HTTP_IF_NONE_MATCH' => if_none_match }
   end
 end
diff --git a/spec/lib/gitlab/etag_caching/router_spec.rb b/spec/lib/gitlab/etag_caching/router_spec.rb
index 2bb40827fcf3211baa1bd94e307dc1cdda8bd857..f69cb502ca6f34098a3446b04d7281910f5f54cb 100644
--- a/spec/lib/gitlab/etag_caching/router_spec.rb
+++ b/spec/lib/gitlab/etag_caching/router_spec.rb
@@ -2,115 +2,91 @@ require 'spec_helper'
 
 describe Gitlab::EtagCaching::Router do
   it 'matches issue notes endpoint' do
-    request = build_request(
+    result = described_class.match(
       '/my-group/and-subgroup/here-comes-the-project/noteable/issue/1/notes'
     )
 
-    result = described_class.match(request)
-
     expect(result).to be_present
     expect(result.name).to eq 'issue_notes'
   end
 
   it 'matches issue title endpoint' do
-    request = build_request(
+    result = described_class.match(
       '/my-group/my-project/issues/123/realtime_changes'
     )
 
-    result = described_class.match(request)
-
     expect(result).to be_present
     expect(result.name).to eq 'issue_title'
   end
 
   it 'matches project pipelines endpoint' do
-    request = build_request(
+    result = described_class.match(
       '/my-group/my-project/pipelines.json'
     )
 
-    result = described_class.match(request)
-
     expect(result).to be_present
     expect(result.name).to eq 'project_pipelines'
   end
 
   it 'matches commit pipelines endpoint' do
-    request = build_request(
+    result = described_class.match(
       '/my-group/my-project/commit/aa8260d253a53f73f6c26c734c72fdd600f6e6d4/pipelines.json'
     )
 
-    result = described_class.match(request)
-
     expect(result).to be_present
     expect(result.name).to eq 'commit_pipelines'
   end
 
   it 'matches new merge request pipelines endpoint' do
-    request = build_request(
+    result = described_class.match(
       '/my-group/my-project/merge_requests/new.json'
     )
 
-    result = described_class.match(request)
-
     expect(result).to be_present
     expect(result.name).to eq 'new_merge_request_pipelines'
   end
 
   it 'matches merge request pipelines endpoint' do
-    request = build_request(
+    result = described_class.match(
       '/my-group/my-project/merge_requests/234/pipelines.json'
     )
 
-    result = described_class.match(request)
-
     expect(result).to be_present
     expect(result.name).to eq 'merge_request_pipelines'
   end
 
   it 'matches build endpoint' do
-    request = build_request(
+    result = described_class.match(
       '/my-group/my-project/builds/234.json'
     )
 
-    result = described_class.match(request)
-
     expect(result).to be_present
     expect(result.name).to eq 'project_build'
   end
 
   it 'does not match blob with confusing name' do
-    request = build_request(
+    result = described_class.match(
       '/my-group/my-project/blob/master/pipelines.json'
     )
 
-    result = described_class.match(request)
-
     expect(result).to be_blank
   end
 
   it 'matches the environments path' do
-    request = build_request(
+    result = described_class.match(
       '/my-group/my-project/environments.json'
     )
 
-    result = described_class.match(request)
     expect(result).to be_present
-
     expect(result.name).to eq 'environments'
   end
 
   it 'matches pipeline#show endpoint' do
-    request = build_request(
+    result = described_class.match(
       '/my-group/my-project/pipelines/2.json'
     )
 
-    result = described_class.match(request)
-
     expect(result).to be_present
     expect(result.name).to eq 'project_pipeline'
   end
-
-  def build_request(path)
-    double(path_info: path)
-  end
 end
diff --git a/spec/models/ci/build_spec.rb b/spec/models/ci/build_spec.rb
index b0716e04d3de43f8b3c83048018ae2d3e326dee1..b06e77c34f6b82f2f47075b68f42db8eda720c05 100644
--- a/spec/models/ci/build_spec.rb
+++ b/spec/models/ci/build_spec.rb
@@ -21,6 +21,18 @@ describe Ci::Build, :models do
   it { is_expected.to respond_to(:has_trace?) }
   it { is_expected.to respond_to(:trace) }
 
+  describe '.manual_actions' do
+    let!(:manual_but_created) { create(:ci_build, :manual, status: :created, pipeline: pipeline) }
+    let!(:manual_but_succeeded) { create(:ci_build, :manual, status: :success, pipeline: pipeline) }
+    let!(:manual_action) { create(:ci_build, :manual, pipeline: pipeline) }
+
+    subject { described_class.manual_actions }
+
+    it { is_expected.to include(manual_action) }
+    it { is_expected.to include(manual_but_succeeded) }
+    it { is_expected.not_to include(manual_but_created) }
+  end
+
   describe '#actionize' do
     context 'when build is a created' do
       before do
@@ -926,6 +938,10 @@ describe Ci::Build, :models do
     context 'when other build is retried' do
       let!(:retried_build) { Ci::Build.retry(other_build, user) }
 
+      before do
+        retried_build.success
+      end
+
       it 'returns a retried build' do
         is_expected.to contain_exactly(retried_build)
       end
diff --git a/spec/models/commit_spec.rb b/spec/models/commit_spec.rb
index 140fd2720bf8f43a11888c946962c39b44590296..ba247dcc5cf8754064c8e42764b7b66c07ec6022 100644
--- a/spec/models/commit_spec.rb
+++ b/spec/models/commit_spec.rb
@@ -20,8 +20,8 @@ describe Commit, models: true do
     end
 
     it 'caches the author' do
+      allow(RequestStore).to receive(:active?).and_return(true)
       user = create(:user, email: commit.author_email)
-      expect(RequestStore).to receive(:active?).twice.and_return(true)
       expect_any_instance_of(Commit).to receive(:find_author_by_any_email).and_call_original
 
       expect(commit.author).to eq(user)
diff --git a/spec/models/deployment_spec.rb b/spec/models/deployment_spec.rb
index 6f0d2db23c784de2f3649edc60f6f4aaa245d2bf..aad215d5f416fbf1a8385697f5726490f92c258d 100644
--- a/spec/models/deployment_spec.rb
+++ b/spec/models/deployment_spec.rb
@@ -102,7 +102,7 @@ describe Deployment, models: true do
     end
 
     context 'with other actions' do
-      let!(:close_action) { create(:ci_build, pipeline: build.pipeline, name: 'close_app', when: :manual) }
+      let!(:close_action) { create(:ci_build, :manual, pipeline: build.pipeline, name: 'close_app') }
 
       context 'when matching action is defined' do
         let(:deployment) { FactoryGirl.build(:deployment, deployable: build, on_stop: 'close_other_app') }
@@ -130,7 +130,7 @@ describe Deployment, models: true do
     context 'when matching action is defined' do
       let(:build) { create(:ci_build) }
       let(:deployment) { FactoryGirl.build(:deployment, deployable: build, on_stop: 'close_app') }
-      let!(:close_action) { create(:ci_build, pipeline: build.pipeline, name: 'close_app', when: :manual) }
+      let!(:close_action) { create(:ci_build, :manual, pipeline: build.pipeline, name: 'close_app') }
 
       it { is_expected.to be_truthy }
     end
diff --git a/spec/models/environment_spec.rb b/spec/models/environment_spec.rb
index fe69c8e351d2d5fa1f34205b9b812fb7d84b6aa5..f8123cb518ebe79d956dbc83fa6f394e8f0181da 100644
--- a/spec/models/environment_spec.rb
+++ b/spec/models/environment_spec.rb
@@ -170,7 +170,7 @@ describe Environment, models: true do
     context 'when matching action is defined' do
       let(:build) { create(:ci_build) }
       let!(:deployment) { create(:deployment, environment: environment, deployable: build, on_stop: 'close_app') }
-      let!(:close_action) { create(:ci_build, pipeline: build.pipeline, name: 'close_app', when: :manual) }
+      let!(:close_action) { create(:ci_build, :manual, pipeline: build.pipeline, name: 'close_app') }
 
       context 'when environment is available' do
         before do
diff --git a/spec/policies/project_policy_spec.rb b/spec/policies/project_policy_spec.rb
index 0d3af1f449999cedf0067f3a6cc57dd124d2fed0..848fd547e104860f50e04200fc47c55f51490877 100644
--- a/spec/policies/project_policy_spec.rb
+++ b/spec/policies/project_policy_spec.rb
@@ -139,6 +139,18 @@ describe ProjectPolicy, models: true do
           is_expected.not_to include(:read_build, :read_pipeline)
         end
       end
+
+      context 'when builds are disabled' do
+        before do
+          project.project_feature.update(
+            builds_access_level: ProjectFeature::DISABLED)
+        end
+
+        it do
+          is_expected.not_to include(:read_build)
+          is_expected.to include(:read_pipeline)
+        end
+      end
     end
 
     context 'reporter' do
diff --git a/spec/requests/api/runner_spec.rb b/spec/requests/api/runner_spec.rb
index be83514ed9c2859157c46ba3a1b679415dcb0f57..9556c99dea153428a87d788360d89a31f969bd2e 100644
--- a/spec/requests/api/runner_spec.rb
+++ b/spec/requests/api/runner_spec.rb
@@ -431,8 +431,29 @@ describe API::Runner do
               expect(response).to have_http_status(201)
               expect(json_response['id']).to eq(test_job.id)
               expect(json_response['dependencies'].count).to eq(2)
-              expect(json_response['dependencies']).to include({ 'id' => job.id, 'name' => job.name, 'token' => job.token },
-                                                               { 'id' => job2.id, 'name' => job2.name, 'token' => job2.token })
+              expect(json_response['dependencies']).to include(
+                { 'id' => job.id, 'name' => job.name, 'token' => job.token },
+                { 'id' => job2.id, 'name' => job2.name, 'token' => job2.token })
+            end
+          end
+
+          context 'when pipeline have jobs with artifacts' do
+            let!(:job) { create(:ci_build_tag, :artifacts, pipeline: pipeline, name: 'spinach', stage: 'test', stage_idx: 0) }
+            let!(:test_job) { create(:ci_build, pipeline: pipeline, name: 'deploy', stage: 'deploy', stage_idx: 1) }
+
+            before do
+              job.success
+            end
+
+            it 'returns dependent jobs' do
+              request_job
+
+              expect(response).to have_http_status(201)
+              expect(json_response['id']).to eq(test_job.id)
+              expect(json_response['dependencies'].count).to eq(1)
+              expect(json_response['dependencies']).to include(
+                { 'id' => job.id, 'name' => job.name, 'token' => job.token,
+                  'artifacts_file' => { 'filename' => 'ci_build_artifacts.zip', 'size' => 106365 } })
             end
           end
 
diff --git a/spec/serializers/build_details_entity_spec.rb b/spec/serializers/build_details_entity_spec.rb
index e2511e8968c5fe93de52dfd84d44535253d3ee2e..b92c1c28ba871f29abede4ab6886b3c3835ac3ac 100644
--- a/spec/serializers/build_details_entity_spec.rb
+++ b/spec/serializers/build_details_entity_spec.rb
@@ -3,8 +3,8 @@ require 'spec_helper'
 describe BuildDetailsEntity do
   set(:user) { create(:admin) }
 
-  it 'inherits from BuildEntity' do
-    expect(described_class).to be < BuildEntity
+  it 'inherits from JobEntity' do
+    expect(described_class).to be < JobEntity
   end
 
   describe '#as_json' do
@@ -29,7 +29,7 @@ describe BuildDetailsEntity do
 
       it 'contains the needed key value pairs' do
         expect(subject).to include(:coverage, :erased_at, :duration)
-        expect(subject).to include(:artifacts, :runner, :pipeline)
+        expect(subject).to include(:runner, :pipeline)
         expect(subject).to include(:raw_path, :merge_request)
         expect(subject).to include(:new_issue_path)
       end
diff --git a/spec/serializers/build_entity_spec.rb b/spec/serializers/job_entity_spec.rb
similarity index 56%
rename from spec/serializers/build_entity_spec.rb
rename to spec/serializers/job_entity_spec.rb
index 46d43a80ef79d43b8b8e3f9ed7008fbd330ec4fe..ce29bc9cd054b5ef5ee358d47cd21cc5975eba32 100644
--- a/spec/serializers/build_entity_spec.rb
+++ b/spec/serializers/job_entity_spec.rb
@@ -1,9 +1,9 @@
 require 'spec_helper'
 
-describe BuildEntity do
+describe JobEntity do
   let(:user) { create(:user) }
-  let(:build) { create(:ci_build, :failed) }
-  let(:project) { build.project }
+  let(:job) { create(:ci_build) }
+  let(:project) { job.project }
   let(:request) { double('request') }
 
   before do
@@ -11,14 +11,13 @@ describe BuildEntity do
   end
 
   let(:entity) do
-    described_class.new(build, request: request)
+    described_class.new(job, request: request)
   end
 
   subject { entity.as_json }
 
-  it 'contains paths to build page and retry action' do
-    expect(subject).to include(:build_path, :retry_path)
-    expect(subject[:retry_path]).not_to be_nil
+  it 'contains paths to job page action' do
+    expect(subject).to include(:build_path)
   end
 
   it 'does not contain sensitive information' do
@@ -27,7 +26,7 @@ describe BuildEntity do
   end
 
   it 'contains whether it is playable' do
-    expect(subject[:playable]).to eq build.playable?
+    expect(subject[:playable]).to eq job.playable?
   end
 
   it 'contains timestamps' do
@@ -39,7 +38,17 @@ describe BuildEntity do
     expect(subject[:status]).to include :icon, :favicon, :text, :label
   end
 
-  context 'when build is a regular job' do
+  context 'when job is retryable' do
+    before do
+      job.update(status: :failed)
+    end
+
+    it 'contains retry path' do
+      expect(subject).to include(:retry_path)
+    end
+  end
+
+  context 'when job is a regular job' do
     it 'does not contain path to play action' do
       expect(subject).not_to include(:play_path)
     end
@@ -49,8 +58,8 @@ describe BuildEntity do
     end
   end
 
-  context 'when build is a manual action' do
-    let(:build) { create(:ci_build, :manual) }
+  context 'when job is a manual action' do
+    let(:job) { create(:ci_build, :manual) }
 
     context 'when user is allowed to trigger action' do
       before do
@@ -79,4 +88,25 @@ describe BuildEntity do
       end
     end
   end
+
+  context 'when job is generic commit status' do
+    let(:job) { create(:generic_commit_status, target_url: 'http://google.com') }
+
+    it 'contains paths to target action' do
+      expect(subject).to include(:build_path)
+    end
+
+    it 'does not contain paths to other action paths' do
+      expect(subject).not_to include(:retry_path, :cancel_path, :play_path)
+    end
+
+    it 'contains timestamps' do
+      expect(subject).to include(:created_at, :updated_at)
+    end
+
+    it 'contains details' do
+      expect(subject).to include :status
+      expect(subject[:status]).to include :icon, :favicon, :text, :label
+    end
+  end
 end
diff --git a/spec/serializers/pipeline_details_entity_spec.rb b/spec/serializers/pipeline_details_entity_spec.rb
index 03cc5ae9b63a7512a3ed1b54980d4e93ad350048..5cb9b9945b67d8fc2590edf23245ef6c92d67785 100644
--- a/spec/serializers/pipeline_details_entity_spec.rb
+++ b/spec/serializers/pipeline_details_entity_spec.rb
@@ -91,6 +91,20 @@ describe PipelineDetailsEntity do
       end
     end
 
+    context 'when pipeline has commit statuses' do
+      let(:pipeline) { create(:ci_empty_pipeline) }
+    
+      before do
+        create(:generic_commit_status, pipeline: pipeline)
+      end
+
+      it 'contains stages' do
+        expect(subject).to include(:details)
+        expect(subject[:details]).to include(:stages)
+        expect(subject[:details][:stages].first).to include(name: 'external')
+      end
+    end
+
     context 'when pipeline has YAML errors' do
       let(:pipeline) do
         create(:ci_pipeline, config: { rspec: { invalid: :value } })
diff --git a/spec/serializers/stage_entity_spec.rb b/spec/serializers/stage_entity_spec.rb
index 64b3217b8092b688e7294a80ef17fa9698e457c5..40e303f7b899323f795fe46e42cc23f19f496804 100644
--- a/spec/serializers/stage_entity_spec.rb
+++ b/spec/serializers/stage_entity_spec.rb
@@ -54,6 +54,17 @@ describe StageEntity do
       it 'exposes the group key' do
         expect(subject).to include :groups
       end
+
+      context 'and contains commit status' do
+        before do
+          create(:generic_commit_status, pipeline: pipeline, stage: 'test')
+        end
+
+        it 'contains commit status' do
+          groups = subject[:groups].map { |group| group[:name] }
+          expect(groups).to include('generic')
+        end
+      end
     end
   end
 end
diff --git a/spec/support/capybara.rb b/spec/support/capybara.rb
index b8ca8f22a3d2cb53a8b926fc05b0aed4cc110ad8..c34e76fa72fc5c1fd29c6aa76e4929a1d2fca6bc 100644
--- a/spec/support/capybara.rb
+++ b/spec/support/capybara.rb
@@ -14,8 +14,10 @@ Capybara.register_driver :poltergeist do |app|
     js_errors: true,
     timeout: timeout,
     window_size: [1366, 768],
+    url_whitelist: %w[localhost 127.0.0.1],
+    url_blacklist: %w[.mp4 .png .gif .avi .bmp .jpg .jpeg],
     phantomjs_options: [
-      '--load-images=no'
+      '--load-images=yes'
     ]
   )
 end
diff --git a/spec/support/target_branch_helpers.rb b/spec/support/target_branch_helpers.rb
deleted file mode 100644
index 01d1c53fe6cbc28d4ab2da3533748fb0531b43f0..0000000000000000000000000000000000000000
--- a/spec/support/target_branch_helpers.rb
+++ /dev/null
@@ -1,16 +0,0 @@
-module TargetBranchHelpers
-  def select_branch(name)
-    first('button.js-target-branch').click
-    wait_for_requests
-    all('a[data-group="Branches"]').find do |el|
-      el.text == name
-    end.click
-  end
-
-  def create_new_branch(name)
-    first('button.js-target-branch').click
-    click_link 'Create new branch'
-    fill_in 'new_branch_name', with: name
-    click_button 'Create'
-  end
-end
diff --git a/spec/support/wait_for_requests.rb b/spec/support/wait_for_requests.rb
index 05ec9026141f947439b99102e04831c9a85651f2..1cbe609c0e029437dc5da2a096d73ab187ca5a27 100644
--- a/spec/support/wait_for_requests.rb
+++ b/spec/support/wait_for_requests.rb
@@ -7,7 +7,7 @@ module WaitForRequests
   def block_and_wait_for_requests_complete
     Gitlab::Testing::RequestBlockerMiddleware.block_requests!
     wait_for('pending requests complete') do
-      Gitlab::Testing::RequestBlockerMiddleware.num_active_requests.zero?
+      Gitlab::Testing::RequestBlockerMiddleware.num_active_requests.zero? && finished_all_requests?
     end
   ensure
     Gitlab::Testing::RequestBlockerMiddleware.allow_requests!
@@ -40,13 +40,13 @@ module WaitForRequests
   end
 
   def finished_all_vue_resource_requests?
-    page.evaluate_script('window.activeVueResources || 0').zero?
+    Capybara.page.evaluate_script('window.activeVueResources || 0').zero?
   end
 
   def finished_all_ajax_requests?
-    return true if page.evaluate_script('typeof jQuery === "undefined"')
+    return true if Capybara.page.evaluate_script('typeof jQuery === "undefined"')
 
-    page.evaluate_script('jQuery.active').zero?
+    Capybara.page.evaluate_script('jQuery.active').zero?
   end
 
   def javascript_test?
diff --git a/spec/uploaders/artifact_uploader_spec.rb b/spec/uploaders/artifact_uploader_spec.rb
index 24e2e3a9f0eb281e269e6ee832167090b3ee9a0c..2a3bd0e3bb2523c6d9caabc118019fd7f0efe269 100644
--- a/spec/uploaders/artifact_uploader_spec.rb
+++ b/spec/uploaders/artifact_uploader_spec.rb
@@ -17,22 +17,45 @@ describe ArtifactUploader do
 
   describe '.artifacts_upload_path' do
     subject { described_class.artifacts_upload_path }
-    
+
     it { is_expected.to start_with(path) }
     it { is_expected.to end_with('tmp/uploads/') }
   end
 
   describe '#store_dir' do
     subject { uploader.store_dir }
-    
+
     it { is_expected.to start_with(path) }
     it { is_expected.to end_with("#{job.project_id}/#{job.id}") }
   end
 
   describe '#cache_dir' do
     subject { uploader.cache_dir }
-    
+
+    it { is_expected.to start_with(path) }
+    it { is_expected.to end_with('/tmp/cache') }
+  end
+
+  describe '#work_dir' do
+    subject { uploader.work_dir }
+
     it { is_expected.to start_with(path) }
-    it { is_expected.to end_with('tmp/cache') }
+    it { is_expected.to end_with('/tmp/work') }
+  end
+
+  describe '#filename' do
+    # we need to use uploader, as this makes to use mounter
+    # which initialises uploader.file object
+    let(:uploader) { job.artifacts_file }
+
+    subject { uploader.filename }
+
+    it { is_expected.to be_nil }
+
+    context 'with artifacts' do
+      let(:job) { create(:ci_build, :artifacts) }
+
+      it { is_expected.not_to be_nil }
+    end
   end
 end
diff --git a/spec/uploaders/gitlab_uploader_spec.rb b/spec/uploaders/gitlab_uploader_spec.rb
index 78e9d9cf46c1cce61976b72b240a8b8a225a915f..a144b39f74f049b6fb4e85f355d5242faa18be1d 100644
--- a/spec/uploaders/gitlab_uploader_spec.rb
+++ b/spec/uploaders/gitlab_uploader_spec.rb
@@ -53,4 +53,19 @@ describe GitlabUploader do
       expect(subject.move_to_store).to eq(true)
     end
   end
+
+  describe '#cache!' do
+    it 'moves the file from the working directory to the cache directory' do
+      # One to get the work dir, the other to remove it
+      expect(subject).to receive(:workfile_path).exactly(2).times.and_call_original
+      # Test https://github.com/carrierwavesubject/carrierwave/blob/v1.0.0/lib/carrierwave/sanitized_file.rb#L200
+      expect(FileUtils).to receive(:mv).with(anything, /^#{subject.work_dir}/).and_call_original
+      expect(FileUtils).to receive(:mv).with(/^#{subject.work_dir}/, /#{subject.cache_dir}/).and_call_original
+
+      fixture = Rails.root.join('spec', 'fixtures', 'rails_sample.jpg')
+      subject.cache!(fixture_file_upload(fixture))
+
+      expect(subject.file.path).to match(/#{subject.cache_dir}/)
+    end
+  end
 end
diff --git a/spec/uploaders/lfs_object_uploader_spec.rb b/spec/uploaders/lfs_object_uploader_spec.rb
index c3b72e7d677e39c01c824794a5f1d85ec033d087..7088bc23334c9904be8f8bc6e79485609cb0ef99 100644
--- a/spec/uploaders/lfs_object_uploader_spec.rb
+++ b/spec/uploaders/lfs_object_uploader_spec.rb
@@ -1,21 +1,9 @@
 require 'spec_helper'
 
 describe LfsObjectUploader do
-  let(:uploader) { described_class.new(build_stubbed(:empty_project)) }
-
-  describe '#cache!' do
-    it 'caches the file in the cache directory' do
-      # One to get the work dir, the other to remove it
-      expect(uploader).to receive(:workfile_path).exactly(2).times.and_call_original
-      expect(FileUtils).to receive(:mv).with(anything, /^#{uploader.work_dir}/).and_call_original
-      expect(FileUtils).to receive(:mv).with(/^#{uploader.work_dir}/, /^#{uploader.cache_dir}/).and_call_original
-
-      fixture = Rails.root.join('spec', 'fixtures', 'rails_sample.jpg')
-      uploader.cache!(fixture_file_upload(fixture))
-
-      expect(uploader.file.path).to start_with(uploader.cache_dir)
-    end
-  end
+  let(:lfs_object) { create(:lfs_object, :with_file) }
+  let(:uploader) { described_class.new(lfs_object) }
+  let(:path) { Gitlab.config.lfs.storage_path }
 
   describe '#move_to_cache' do
     it 'is true' do
@@ -28,4 +16,25 @@ describe LfsObjectUploader do
       expect(uploader.move_to_store).to eq(true)
     end
   end
+
+  describe '#store_dir' do
+    subject { uploader.store_dir }
+
+    it { is_expected.to start_with(path) }
+    it { is_expected.to end_with("#{lfs_object.oid[0, 2]}/#{lfs_object.oid[2, 2]}") }
+  end
+
+  describe '#cache_dir' do
+    subject { uploader.cache_dir }
+
+    it { is_expected.to start_with(path) }
+    it { is_expected.to end_with('/tmp/cache') }
+  end
+
+  describe '#work_dir' do
+    subject { uploader.work_dir }
+
+    it { is_expected.to start_with(path) }
+    it { is_expected.to end_with('/tmp/work') }
+  end
 end
diff --git a/spec/workers/post_receive_spec.rb b/spec/workers/post_receive_spec.rb
index f4bc63bcc6a1c92dd22e2198ffa73acd7407408c..44163c735ba390b1677b3c98bb09f8c9534039d6 100644
--- a/spec/workers/post_receive_spec.rb
+++ b/spec/workers/post_receive_spec.rb
@@ -94,26 +94,23 @@ describe PostReceive do
         it { expect{ subject }.not_to change{ Ci::Pipeline.count } }
       end
     end
-  end
 
-  describe '#process_repository_update' do
-    let(:changes) {'123456 789012 refs/heads/tést'}
-    let(:fake_hook_data) do
-      { event_name: 'repository_update' }
-    end
+    context 'after project changes hooks' do
+      let(:changes) { '123456 789012 refs/heads/tést' }
+      let(:fake_hook_data) { Hash.new(event_name: 'repository_update') }
 
-    before do
-      allow_any_instance_of(Gitlab::GitPostReceive).to receive(:identify).and_return(project.owner)
-      allow_any_instance_of(Gitlab::DataBuilder::Repository).to receive(:update).and_return(fake_hook_data)
-      # silence hooks so we can isolate
-      allow_any_instance_of(Key).to receive(:post_create_hook).and_return(true)
-      allow(subject).to receive(:process_project_changes).and_return(true)
-    end
+      before do
+        allow_any_instance_of(Gitlab::DataBuilder::Repository).to receive(:update).and_return(fake_hook_data)
+        # silence hooks so we can isolate
+        allow_any_instance_of(Key).to receive(:post_create_hook).and_return(true)
+        allow_any_instance_of(GitPushService).to receive(:execute).and_return(true)
+      end
 
-    it 'calls SystemHooksService' do
-      expect_any_instance_of(SystemHooksService).to receive(:execute_hooks).with(fake_hook_data, :repository_update_hooks).and_return(true)
+      it 'calls SystemHooksService' do
+        expect_any_instance_of(SystemHooksService).to receive(:execute_hooks).with(fake_hook_data, :repository_update_hooks).and_return(true)
 
-      subject.perform(pwd(project), key_id, base64_changes)
+        described_class.new.perform(project_identifier, key_id, base64_changes)
+      end
     end
   end
 
diff --git a/vendor/assets/javascripts/peek.js b/vendor/assets/javascripts/peek.js
new file mode 100644
index 0000000000000000000000000000000000000000..f7e77de34ff36138bf2d754b3664c05ab5d68378
--- /dev/null
+++ b/vendor/assets/javascripts/peek.js
@@ -0,0 +1,78 @@
+(function($) {
+  var fetchRequestResults, getRequestId, peekEnabled, toggleBar, updatePerformanceBar;
+  getRequestId = function() {
+    return $('#peek').data('request-id');
+  };
+  peekEnabled = function() {
+    return $('#peek').length;
+  };
+  updatePerformanceBar = function(results) {
+    var key, label, data, table, html, tr, duration_td, sql_td, strong;
+
+    Object.keys(results.data).forEach(function(key) {
+      Object.keys(results.data[key]).forEach(function(label) {
+        data = results.data[key][label];
+
+        if (label == 'queries') {
+          table = document.createElement('table');
+
+          for (var i = 0; i < data.length; i += 1) {
+            tr = document.createElement('tr');
+            duration_td = document.createElement('td');
+            sql_td = document.createElement('td');
+            strong = document.createElement('strong');
+
+            strong.append(data[i]['duration'] + 'ms');
+            duration_td.appendChild(strong);
+            tr.appendChild(duration_td);
+
+            sql_td.appendChild(document.createTextNode(data[i]['sql']));
+            tr.appendChild(sql_td);
+
+            table.appendChild(tr);
+          }
+
+          table.className = 'table';
+          $("[data-defer-to=" + key + "-" + label + "]").html(table);
+        } else {
+          $("[data-defer-to=" + key + "-" + label + "]").text(results.data[key][label]);
+        }
+      });
+    });
+    return $(document).trigger('peek:render', [getRequestId(), results]);
+  };
+  toggleBar = function(event) {
+    var wrapper;
+    if ($(event.target).is(':input')) {
+      return;
+    }
+    if (event.which === 96 && !event.metaKey) {
+      wrapper = $('#peek');
+      if (wrapper.hasClass('disabled')) {
+        wrapper.removeClass('disabled');
+        return document.cookie = "peek=true; path=/";
+      } else {
+        wrapper.addClass('disabled');
+        return document.cookie = "peek=false; path=/";
+      }
+    }
+  };
+  fetchRequestResults = function() {
+    return $.ajax('/-/peek/results', {
+      data: {
+        request_id: getRequestId()
+      },
+      success: function(data, textStatus, xhr) {
+        return updatePerformanceBar(data);
+      },
+      error: function(xhr, textStatus, error) {}
+    });
+  };
+  $(document).on('keypress', toggleBar);
+  $(document).on('peek:update', fetchRequestResults);
+  return $(function() {
+    if (peekEnabled()) {
+      return $(this).trigger('peek:update');
+    }
+  });
+})(jQuery);
diff --git a/vendor/assets/javascripts/peek.performance_bar.js b/vendor/assets/javascripts/peek.performance_bar.js
new file mode 100644
index 0000000000000000000000000000000000000000..6ed86dce2f2d4e600c6d82baaa1989b20dac343d
--- /dev/null
+++ b/vendor/assets/javascripts/peek.performance_bar.js
@@ -0,0 +1,182 @@
+var PerformanceBar, ajaxStart, renderPerformanceBar, updateStatus;
+
+PerformanceBar = (function() {
+  PerformanceBar.prototype.appInfo = null;
+
+  PerformanceBar.prototype.width = null;
+
+  PerformanceBar.formatTime = function(value) {
+    if (value >= 1000) {
+      return ((value / 1000).toFixed(3)) + "s";
+    } else {
+      return (value.toFixed(0)) + "ms";
+    }
+  };
+
+  function PerformanceBar(options) {
+    var k, v;
+    if (options == null) {
+      options = {};
+    }
+    this.el = $('#peek-view-performance-bar .performance-bar');
+    for (k in options) {
+      v = options[k];
+      this[k] = v;
+    }
+    if (this.width == null) {
+      this.width = this.el.width();
+    }
+    if (this.timing == null) {
+      this.timing = window.performance.timing;
+    }
+  }
+
+  PerformanceBar.prototype.render = function(serverTime) {
+    var networkTime, perfNetworkTime;
+    if (serverTime == null) {
+      serverTime = 0;
+    }
+    this.el.empty();
+    this.addBar('frontend', '#90d35b', 'domLoading', 'domInteractive');
+    perfNetworkTime = this.timing.responseEnd - this.timing.requestStart;
+    if (serverTime && serverTime <= perfNetworkTime) {
+      networkTime = perfNetworkTime - serverTime;
+      this.addBar('latency / receiving', '#f1faff', this.timing.requestStart + serverTime, this.timing.requestStart + serverTime + networkTime);
+      this.addBar('app', '#90afcf', this.timing.requestStart, this.timing.requestStart + serverTime, this.appInfo);
+    } else {
+      this.addBar('backend', '#c1d7ee', 'requestStart', 'responseEnd');
+    }
+    this.addBar('tcp / ssl', '#45688e', 'connectStart', 'connectEnd');
+    this.addBar('redirect', '#0c365e', 'redirectStart', 'redirectEnd');
+    this.addBar('dns', '#082541', 'domainLookupStart', 'domainLookupEnd');
+    return this.el;
+  };
+
+  PerformanceBar.prototype.isLoaded = function() {
+    return this.timing.domInteractive;
+  };
+
+  PerformanceBar.prototype.start = function() {
+    return this.timing.navigationStart;
+  };
+
+  PerformanceBar.prototype.end = function() {
+    return this.timing.domInteractive;
+  };
+
+  PerformanceBar.prototype.total = function() {
+    return this.end() - this.start();
+  };
+
+  PerformanceBar.prototype.addBar = function(name, color, start, end, info) {
+    var bar, left, offset, time, title, width;
+    if (typeof start === 'string') {
+      start = this.timing[start];
+    }
+    if (typeof end === 'string') {
+      end = this.timing[end];
+    }
+    if (!((start != null) && (end != null))) {
+      return;
+    }
+    time = end - start;
+    offset = start - this.start();
+    left = this.mapH(offset);
+    width = this.mapH(time);
+    title = name + ": " + (PerformanceBar.formatTime(time));
+    bar = $('<li></li>', {
+      'data-title': title,
+      'data-toggle': 'tooltip',
+      'data-container': 'body'
+    });
+    bar.css({
+      width: width + "px",
+      left: left + "px",
+      background: color
+    });
+    return this.el.append(bar);
+  };
+
+  PerformanceBar.prototype.mapH = function(offset) {
+    return offset * (this.width / this.total());
+  };
+
+  return PerformanceBar;
+
+})();
+
+renderPerformanceBar = function() {
+  var bar, resp, span, time;
+  resp = $('#peek-server_response_time');
+  time = Math.round(resp.data('time') * 1000);
+  bar = new PerformanceBar;
+  bar.render(time);
+  span = $('<span>', {
+    'data-toggle': 'tooltip',
+    'data-title': 'Total navigation time for this page.',
+    'data-container': 'body'
+  }).text(PerformanceBar.formatTime(bar.total()));
+  return updateStatus(span);
+};
+
+updateStatus = function(html) {
+  return $('#serverstats').html(html);
+};
+
+ajaxStart = null;
+
+$(document).on('pjax:start page:fetch turbolinks:request-start', function(event) {
+  return ajaxStart = event.timeStamp;
+});
+
+$(document).on('pjax:end page:load turbolinks:load', function(event, xhr) {
+  var ajaxEnd, serverTime, total;
+  if (ajaxStart == null) {
+    return;
+  }
+  ajaxEnd = event.timeStamp;
+  total = ajaxEnd - ajaxStart;
+  serverTime = xhr ? parseInt(xhr.getResponseHeader('X-Runtime')) : 0;
+  return setTimeout(function() {
+    var bar, now, span, tech;
+    now = new Date().getTime();
+    bar = new PerformanceBar({
+      timing: {
+        requestStart: ajaxStart,
+        responseEnd: ajaxEnd,
+        domLoading: ajaxEnd,
+        domInteractive: now
+      },
+      isLoaded: function() {
+        return true;
+      },
+      start: function() {
+        return ajaxStart;
+      },
+      end: function() {
+        return now;
+      }
+    });
+    bar.render(serverTime);
+    if ($.fn.pjax != null) {
+      tech = 'PJAX';
+    } else {
+      tech = 'Turbolinks';
+    }
+    span = $('<span>', {
+      'data-toggle': 'tooltip',
+      'data-title': tech + " navigation time",
+      'data-container': 'body'
+    }).text(PerformanceBar.formatTime(total));
+    updateStatus(span);
+    return ajaxStart = null;
+  }, 0);
+});
+
+$(function() {
+  if (window.performance) {
+    return renderPerformanceBar();
+  } else {
+    return $('#peek-view-performance-bar').remove();
+  }
+});
diff --git a/vendor/assets/stylesheets/peek.scss b/vendor/assets/stylesheets/peek.scss
new file mode 100644
index 0000000000000000000000000000000000000000..f1845fb9044e540b37a96c070501604690ef9818
--- /dev/null
+++ b/vendor/assets/stylesheets/peek.scss
@@ -0,0 +1,94 @@
+//= require peek/views/performance_bar
+//= require peek/views/rblineprof
+
+header.navbar-gitlab.with-peek {
+  top: 35px;
+}
+
+#peek {
+  height: 35px;
+  background: #000;
+  line-height: 35px;
+  color: #999;
+
+  &.disabled {
+    display: none;
+  }
+
+  &.production {
+    background-color: #222;
+  }
+
+  &.staging {
+    background-color: #291430;
+  }
+
+  &.development {
+    background-color: #4c1210;
+  }
+
+  .wrapper {
+    width: 800px;
+    margin: 0 auto;
+  }
+
+  // UI Elements
+  .bucket {
+    background: #111;
+    display: inline-block;
+    padding: 4px 6px;
+    font-family: Consolas, "Liberation Mono", Courier, monospace;
+    line-height: 1;
+    color: #ccc;
+    border-radius: 3px;
+    box-shadow: 0 1px 0 rgba(255,255,255,.2), inset 0 1px 2px rgba(0,0,0,.25);
+
+    .hidden {
+      display: none;
+    }
+
+    &:hover .hidden {
+      display: inline;
+    }
+  }
+
+  strong {
+    color: #fff;
+  }
+
+  table {
+    strong {
+      color: #000;
+    }
+  }
+
+  .view {
+    margin-right: 15px;
+    float: left;
+
+    &:last-child {
+      margin-right: 0;
+    }
+  }
+
+  .css-truncate {
+    &.css-truncate-target,
+    .css-truncate-target {
+      display: inline-block;
+      max-width: 125px;
+      overflow: hidden;
+      text-overflow: ellipsis;
+      white-space: nowrap;
+      vertical-align: top;
+    }
+
+    &.expandable:hover .css-truncate-target,
+    &.expandable:hover.css-truncate-target {
+      max-width: 10000px !important;
+    }
+  }
+}
+
+#modal-peek-pg-queries-content {
+  color: #000;
+}