diff --git a/app/assets/javascripts/blob/viewer/index.js b/app/assets/javascripts/blob/viewer/index.js
index d06387c0f4d78baf6e7ccab4ac6e4949ae5ed8ac..3eddf595f30ebe53b0bf85dc465b251225b39de8 100644
--- a/app/assets/javascripts/blob/viewer/index.js
+++ b/app/assets/javascripts/blob/viewer/index.js
@@ -78,7 +78,7 @@ export default class BlobViewer {
     .fail(() => new Flash('Error loading source view'))
     .done((data) => {
       viewer.innerHTML = data.html;
-      $(viewer).syntaxHighlight();
+      $(viewer).renderGFM();
 
       viewer.setAttribute('data-loaded', 'true');
 
diff --git a/app/assets/javascripts/dispatcher.js b/app/assets/javascripts/dispatcher.js
index bf9fbf2ecb529d5b19daff5d9a7a493ebc0d73d6..44e16e52296300e4a13363f9825f712ca55893f8 100644
--- a/app/assets/javascripts/dispatcher.js
+++ b/app/assets/javascripts/dispatcher.js
@@ -124,7 +124,7 @@ const ShortcutsBlob = require('./shortcuts_blob');
           break;
         case 'projects:merge_requests:index':
         case 'projects:issues:index':
-          if (gl.FilteredSearchManager) {
+          if (gl.FilteredSearchManager && document.querySelector('.filtered-search')) {
             const filteredSearchManager = new gl.FilteredSearchManager(
               page === 'projects:issues:index' ? 'issues' : 'merge_requests',
             );
diff --git a/app/assets/javascripts/issue_show/issue_title_description.vue b/app/assets/javascripts/issue_show/issue_title_description.vue
index 8a7a813efd88c4d3310593ed739d6d6f2980f5ad..a8b1b99d166b9522e2d5d089a685e9adfdd8d7b0 100644
--- a/app/assets/javascripts/issue_show/issue_title_description.vue
+++ b/app/assets/javascripts/issue_show/issue_title_description.vue
@@ -4,6 +4,7 @@ import Poll from './../lib/utils/poll';
 import Service from './services/index';
 import tasks from './actions/tasks';
 import edited from './components/edited.vue';
+import normalizeNewlines from '../lib/utils/normalize_newlines';
 
 export default {
   props: {
@@ -105,7 +106,7 @@ export default {
       this.title = title;
       this.description = description;
 
-      this.$nextTick(() => {
+      setTimeout(() => {
         this.updateFlag('titleFlag', false);
         this.updateFlag('descriptionFlag', false);
       });
@@ -119,7 +120,8 @@ export default {
       this.titleText = this.apiData.title_text;
 
       const noTitleChange = this.title === title;
-      const noDescriptionChange = this.description === description;
+      const noDescriptionChange =
+        normalizeNewlines(this.description) === normalizeNewlines(description);
 
       /**
       * since opacity is changed, even if there is no diff for Vue to update
diff --git a/app/assets/javascripts/lib/utils/common_utils.js b/app/assets/javascripts/lib/utils/common_utils.js
index 2f682fbd2fbf8530835df57d1c792df225edae17..7e62773ae6c83e3ba326c77cd2ec531b5b2394ad 100644
--- a/app/assets/javascripts/lib/utils/common_utils.js
+++ b/app/assets/javascripts/lib/utils/common_utils.js
@@ -135,7 +135,10 @@
     gl.utils.getUrlParamsArray = function () {
       // We can trust that each param has one & since values containing & will be encoded
       // Remove the first character of search as it is always ?
-      return window.location.search.slice(1).split('&');
+      return window.location.search.slice(1).split('&').map((param) => {
+        const split = param.split('=');
+        return [decodeURI(split[0]), split[1]].join('=');
+      });
     };
 
     gl.utils.isMetaKey = function(e) {
diff --git a/app/assets/javascripts/lib/utils/normalize_newlines.js b/app/assets/javascripts/lib/utils/normalize_newlines.js
new file mode 100644
index 0000000000000000000000000000000000000000..f155c3f7587f436326b20ca1c83265e32e221316
--- /dev/null
+++ b/app/assets/javascripts/lib/utils/normalize_newlines.js
@@ -0,0 +1,5 @@
+function normalizeNewlines(str) {
+  return str.replace(/(\r|
)?(\n|
)/g, '\n');
+}
+
+export default normalizeNewlines;
diff --git a/app/assets/javascripts/lib/utils/notify.js b/app/assets/javascripts/lib/utils/notify.js
index 66f39122a663f4de230084e87fb5451f4dc6dfa7..973d6119158b94d576aaaaffda780d1324ef6a19 100644
--- a/app/assets/javascripts/lib/utils/notify.js
+++ b/app/assets/javascripts/lib/utils/notify.js
@@ -1,47 +1,48 @@
 /* eslint-disable func-names, space-before-function-paren, wrap-iife, no-var, one-var, one-var-declaration-per-line, consistent-return, prefer-arrow-callback, no-return-assign, object-shorthand, comma-dangle, no-param-reassign, max-len */
 
-(function() {
-  (function(w) {
-    var notificationGranted, notifyMe, notifyPermissions;
-    notificationGranted = function(message, opts, onclick) {
-      var notification;
-      notification = new Notification(message, opts);
-      setTimeout(function() {
-        return notification.close();
-      // Hide the notification after X amount of seconds
-      }, 8000);
-      if (onclick) {
-        return notification.onclick = onclick;
-      }
-    };
-    notifyPermissions = function() {
-      if ('Notification' in window) {
-        return Notification.requestPermission();
-      }
-    };
-    notifyMe = function(message, body, icon, onclick) {
-      var opts;
-      opts = {
-        body: body,
-        icon: icon
-      };
-      // Let's check if the browser supports notifications
-      if (!('Notification' in window)) {
+function notificationGranted(message, opts, onclick) {
+  var notification;
+  notification = new Notification(message, opts);
+  setTimeout(function() {
+    // Hide the notification after X amount of seconds
+    return notification.close();
+  }, 8000);
+
+  return notification.onclick = onclick || notification.close;
+}
 
-      // do nothing
-      } else if (Notification.permission === 'granted') {
-        // If it's okay let's create a notification
+function notifyPermissions() {
+  if ('Notification' in window) {
+    return Notification.requestPermission();
+  }
+}
+
+function notifyMe(message, body, icon, onclick) {
+  var opts;
+  opts = {
+    body: body,
+    icon: icon
+  };
+  // Let's check if the browser supports notifications
+  if (!('Notification' in window)) {
+    // do nothing
+  } else if (Notification.permission === 'granted') {
+    // If it's okay let's create a notification
+    return notificationGranted(message, opts, onclick);
+  } else if (Notification.permission !== 'denied') {
+    return Notification.requestPermission(function(permission) {
+      // If the user accepts, let's create a notification
+      if (permission === 'granted') {
         return notificationGranted(message, opts, onclick);
-      } else if (Notification.permission !== 'denied') {
-        return Notification.requestPermission(function(permission) {
-          // If the user accepts, let's create a notification
-          if (permission === 'granted') {
-            return notificationGranted(message, opts, onclick);
-          }
-        });
       }
-    };
-    w.notify = notifyMe;
-    return w.notifyPermissions = notifyPermissions;
-  })(window);
-}).call(window);
+    });
+  }
+}
+
+const notify = {
+  notificationGranted,
+  notifyPermissions,
+  notifyMe,
+};
+
+export default notify;
diff --git a/app/assets/javascripts/main.js b/app/assets/javascripts/main.js
index abacab9b4c31de3571fc7c8f53daa2728b203b07..3932df5f9932db0cdab6cc85c55f170a162fd11b 100644
--- a/app/assets/javascripts/main.js
+++ b/app/assets/javascripts/main.js
@@ -56,7 +56,6 @@ import './lib/utils/animate';
 import './lib/utils/bootstrap_linked_tabs';
 import './lib/utils/common_utils';
 import './lib/utils/datetime_utility';
-import './lib/utils/notify';
 import './lib/utils/pretty_time';
 import './lib/utils/text_utility';
 import './lib/utils/type_utility';
diff --git a/app/assets/javascripts/merge_request_tabs.js b/app/assets/javascripts/merge_request_tabs.js
index c709730f78f72b9d3eb9380472e442f7b7bf6242..e40d6572b18ab9bfe590e4254abfa2dd5ac40449 100644
--- a/app/assets/javascripts/merge_request_tabs.js
+++ b/app/assets/javascripts/merge_request_tabs.js
@@ -285,7 +285,7 @@ import BlobForkSuggestion from './blob/blob_fork_suggestion';
           // Similar to `toggler_behavior` in the discussion tab
           const hash = window.gl.utils.getLocationHash();
           const anchor = hash && $container.find(`[id="${hash}"]`);
-          if (anchor) {
+          if (anchor && anchor.length > 0) {
             const notesContent = anchor.closest('.notes_content');
             const lineType = notesContent.hasClass('new') ? 'new' : 'old';
             notes.toggleDiffNote({
diff --git a/app/assets/javascripts/notes.js b/app/assets/javascripts/notes.js
index 9fbf72c044ecbc23bae77509fded759125ffa6a2..31f14614179184fa30d9f145ee77fb2508615e4c 100644
--- a/app/assets/javascripts/notes.js
+++ b/app/assets/javascripts/notes.js
@@ -7,6 +7,7 @@
 import $ from 'jquery';
 import Cookies from 'js-cookie';
 import CommentTypeToggle from './comment_type_toggle';
+import normalizeNewlines from './lib/utils/normalize_newlines';
 
 require('./autosave');
 window.autosize = require('vendor/autosize');
@@ -17,10 +18,6 @@ require('vendor/jquery.caret'); // required by jquery.atwho
 require('vendor/jquery.atwho');
 require('./task_list');
 
-const normalizeNewlines = function(str) {
-  return str.replace(/\r\n/g, '\n');
-};
-
 (function() {
   var bind = function(fn, me) { return function() { return fn.apply(me, arguments); }; };
 
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_ready_to_merge.js b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_ready_to_merge.js
index e9619f915538db8a49923d272323ccb1034f2c40..b5a6dc159bb65d73c852bf1391ad219c5c473832 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_ready_to_merge.js
+++ b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_ready_to_merge.js
@@ -13,7 +13,7 @@ export default {
   },
   data() {
     return {
-      removeSourceBranch: true,
+      removeSourceBranch: this.mr.shouldRemoveSourceBranch,
       mergeWhenBuildSucceeds: false,
       useCommitMessageWithDescription: false,
       setToMergeWhenPipelineSucceeds: false,
@@ -70,6 +70,9 @@ export default {
         || this.isApprovalNeeded
         || this.mr.preventMerge);
     },
+    isRemoveSourceBranchButtonDisabled() {
+      return this.isMergeButtonDisabled || !this.mr.canRemoveSourceBranch;
+    },
     shouldShowSquashBeforeMerge() {
       const { commitsCount, enableSquashBeforeMerge } = this.mr;
       return enableSquashBeforeMerge && commitsCount > 1;
@@ -256,8 +259,9 @@ export default {
       <template v-if="isMergeAllowed()">
         <label class="spacing">
           <input
+            id="remove-source-branch-input"
             v-model="removeSourceBranch"
-            :disabled="isMergeButtonDisabled"
+            :disabled="isRemoveSourceBranchButtonDisabled"
             type="checkbox"/> Remove source branch
         </label>
 
diff --git a/app/assets/javascripts/vue_merge_request_widget/dependencies.js b/app/assets/javascripts/vue_merge_request_widget/dependencies.js
index 35e9a5aec1c78ced20ddb24072293ce79336a526..7bd5a9a786dc47e0e682e7be0e9ab3c0ce17980f 100644
--- a/app/assets/javascripts/vue_merge_request_widget/dependencies.js
+++ b/app/assets/javascripts/vue_merge_request_widget/dependencies.js
@@ -30,3 +30,4 @@ export { default as getStateKey } from './ee/stores/get_state_key';
 export { default as mrWidgetOptions } from './ee/mr_widget_options';
 export { default as stateMaps } from './ee/stores/state_maps';
 export { default as SquashBeforeMerge } from './ee/components/states/mr_widget_squash_before_merge';
+export { default as notify } from '../lib/utils/notify';
diff --git a/app/assets/javascripts/vue_merge_request_widget/index.js b/app/assets/javascripts/vue_merge_request_widget/index.js
index cd65ac069c5c4324498606826c503f4dac8665de..43ef468c303a578b3da50934ec8c9bd81ad2bcdf 100644
--- a/app/assets/javascripts/vue_merge_request_widget/index.js
+++ b/app/assets/javascripts/vue_merge_request_widget/index.js
@@ -4,6 +4,8 @@ import {
 } from './dependencies';
 
 document.addEventListener('DOMContentLoaded', () => {
+  gl.mrWidgetData.gitlabLogo = gon.gitlab_logo;
+
   const vm = new Vue(mrWidgetOptions);
 
   window.gl.mrWidget = {
diff --git a/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.js b/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.js
index 2d218925c066d55eea256cd79756e576a8037b64..4ba6bd7b4fabf22fd90119126fd1a31b76e86f75 100644
--- a/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.js
+++ b/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.js
@@ -29,6 +29,7 @@ import {
   eventHub,
   stateMaps,
   SquashBeforeMerge,
+  notify,
 } from './dependencies';
 
 export default {
@@ -79,8 +80,10 @@ export default {
       this.service.checkStatus()
         .then(res => res.json())
         .then((res) => {
+          this.handleNotification(res);
           this.mr.setData(res);
           this.setFavicon();
+
           if (cb) {
             cb.call(null, res);
           }
@@ -138,6 +141,15 @@ export default {
           new Flash('Something went wrong. Please try again.'); // eslint-disable-line
         });
     },
+    handleNotification(data) {
+      if (data.ci_status === this.mr.ciStatus) return;
+
+      const label = data.pipeline.details.status.label;
+      const title = `Pipeline ${label}`;
+      const message = `Pipeline ${label} for "${data.title}"`;
+
+      notify.notifyMe(title, message, this.mr.gitlabLogo);
+    },
     resumePolling() {
       this.pollingInterval.resume();
     },
diff --git a/app/assets/javascripts/vue_merge_request_widget/stores/mr_widget_store.js b/app/assets/javascripts/vue_merge_request_widget/stores/mr_widget_store.js
index cde519e57bc0f5822b7dc600cff1b4ab1b19d9e4..eb214e768a7a1de85b8919b51e99c2e814f8c704 100644
--- a/app/assets/javascripts/vue_merge_request_widget/stores/mr_widget_store.js
+++ b/app/assets/javascripts/vue_merge_request_widget/stores/mr_widget_store.js
@@ -5,6 +5,8 @@ export default class MergeRequestStore {
 
   constructor(data) {
     this.sha = data.diff_head_sha;
+    this.gitlabLogo = data.gitlabLogo;
+
     this.setData(data);
   }
 
@@ -53,7 +55,7 @@ export default class MergeRequestStore {
     this.cancelAutoMergePath = data.cancel_merge_when_pipeline_succeeds_path;
     this.removeWIPPath = data.remove_wip_path;
     this.sourceBranchRemoved = !data.source_branch_exists;
-    this.shouldRemoveSourceBranch = (data.merge_params || {}).should_remove_source_branch || false;
+    this.shouldRemoveSourceBranch = data.remove_source_branch || false;
     this.onlyAllowMergeIfPipelineSucceeds = data.only_allow_merge_if_pipeline_succeeds || false;
     this.mergeWhenPipelineSucceeds = data.merge_when_pipeline_succeeds || false;
     this.mergePath = data.merge_path;
diff --git a/app/assets/stylesheets/framework/awards.scss b/app/assets/stylesheets/framework/awards.scss
index 0db3ac1a60e0aeead785a6e14dd4ad90fba7b428..75907c35b7e900fcc35c6328412c35fa9980ccf5 100644
--- a/app/assets/stylesheets/framework/awards.scss
+++ b/app/assets/stylesheets/framework/awards.scss
@@ -10,7 +10,7 @@
   top: 0;
   margin-top: 3px;
   padding: $gl-padding;
-  z-index: 9;
+  z-index: 300;
   width: 300px;
   font-size: 14px;
   background-color: $white-light;
@@ -110,6 +110,7 @@
 .award-control {
   margin: 0 5px 6px 0;
   outline: 0;
+  position: relative;
 
   &.disabled {
     cursor: default;
@@ -227,8 +228,8 @@
   .award-control-icon-positive,
   .award-control-icon-super-positive {
     position: absolute;
-    left: 11px;
-    bottom: 7px;
+    left: 10px;
+    bottom: 6px;
     opacity: 0;
     @include transition(opacity, transform);
   }
diff --git a/app/assets/stylesheets/framework/filters.scss b/app/assets/stylesheets/framework/filters.scss
index e489870a3ce9a62fd394fb22fa38f6ee46e37d00..96effc38335d6afcf53c27d8a7d56f8f3054cec9 100644
--- a/app/assets/stylesheets/framework/filters.scss
+++ b/app/assets/stylesheets/framework/filters.scss
@@ -479,4 +479,5 @@
 
 .filter-dropdown-loading {
   padding: 8px 16px;
+  text-align: center;
 }
diff --git a/app/assets/stylesheets/framework/timeline.scss b/app/assets/stylesheets/framework/timeline.scss
index 70db19622282c2b02b2a171c8e21026a2b40425c..ddccfc96819a3ba1d90081a0c2a4bfbbda6f12cf 100644
--- a/app/assets/stylesheets/framework/timeline.scss
+++ b/app/assets/stylesheets/framework/timeline.scss
@@ -3,12 +3,6 @@
   margin: 0;
   padding: 0;
 
-  .note-text {
-    p:last-child {
-      margin-bottom: 0;
-    }
-  }
-
   .system-note {
     .note-text {
       color: $gl-text-color !important;
diff --git a/app/assets/stylesheets/pages/builds.scss b/app/assets/stylesheets/pages/builds.scss
index 14a62b6cbf005de0db5083e6fc9de29e02f1fe34..47aa7a1d658be1332a8b55368e8f4809cb6b11a0 100644
--- a/app/assets/stylesheets/pages/builds.scss
+++ b/app/assets/stylesheets/pages/builds.scss
@@ -390,6 +390,10 @@
   .container-fluid.container-limited {
     max-width: 100%;
   }
+
+  .content-wrapper {
+    padding-bottom: 6px;
+  }
 }
 
 .build-detail-row {
diff --git a/app/assets/stylesheets/pages/environments.scss b/app/assets/stylesheets/pages/environments.scss
index e52b58906e16c4c775ac01165aeb65ab03774292..f33e3f8416d0148c93ebe282ea954504da85f306 100644
--- a/app/assets/stylesheets/pages/environments.scss
+++ b/app/assets/stylesheets/pages/environments.scss
@@ -69,6 +69,10 @@
       }
     }
 
+    .btn .text-center {
+      display: inline;
+    }
+
     .commit-title {
       margin: 0;
     }
diff --git a/app/assets/stylesheets/pages/merge_requests.scss b/app/assets/stylesheets/pages/merge_requests.scss
index ddb04b147090b02e524ce691271b410d58c0b16f..2735c5bd98656d1cf8138705acd08a62553d49db 100644
--- a/app/assets/stylesheets/pages/merge_requests.scss
+++ b/app/assets/stylesheets/pages/merge_requests.scss
@@ -510,17 +510,13 @@
       position: absolute;
       border-top: 2px solid $border-color;
       height: 1px;
-      top: 8px;
+      top: 9px;
       width: 8px;
       left: 0;
     }
 
     &:last-child {
       margin-bottom: 0;
-
-      &::before {
-        top: 14px;
-      }
     }
   }
 
@@ -529,7 +525,7 @@
     width: 2px;
     background: $border-color;
     position: absolute;
-    top: -5px;
+    top: -9px;
   }
 }
 
diff --git a/app/assets/stylesheets/pages/notes.scss b/app/assets/stylesheets/pages/notes.scss
index 99bcf612e8fcfa55ddcc11cc300df3f52af2da2a..d513ee7eb0a806296a3826211b04fb356d28334e 100644
--- a/app/assets/stylesheets/pages/notes.scss
+++ b/app/assets/stylesheets/pages/notes.scss
@@ -394,6 +394,12 @@ ul.notes {
   padding-bottom: 8px;
 }
 
+.note-header-author-name {
+  @media (max-width: $screen-xs-max) {
+    display: none;
+  }
+}
+
 .note-headline-light {
   display: inline;
 
@@ -665,7 +671,7 @@ ul.notes {
 .line-resolve-all {
   vertical-align: middle;
   display: inline-block;
-  padding: 6px 10px;
+  padding: 5px 10px 6px;
   background-color: $gray-light;
   border: 1px solid $border-color;
   border-radius: $border-radius-default;
@@ -678,6 +684,10 @@ ul.notes {
 
   .line-resolve-btn {
     margin-right: 5px;
+
+    svg {
+      vertical-align: middle;
+    }
   }
 }
 
@@ -714,6 +724,10 @@ ul.notes {
   }
 }
 
+.line-resolve-text {
+  vertical-align: middle;
+}
+
 .discussion-next-btn {
   svg {
     margin: 0;
@@ -731,9 +745,8 @@ ul.notes {
 // Merge request notes in diffs
 .diff-file {
   // Diff is side by side
-  .notes_content.parallel .note-header .note-headline-light {
+  .notes_content.parallel .note-header .note-header-author-name {
     display: block;
-    position: relative;
   }
   // Diff is inline
   .notes_content .note-header .note-headline-light {
diff --git a/app/assets/stylesheets/pages/pipelines.scss b/app/assets/stylesheets/pages/pipelines.scss
index 041a5f5d85af88633351e893358f3e1fe062de3d..ce3a909d26b3ca4b0d688b41975ff83eaa924541 100644
--- a/app/assets/stylesheets/pages/pipelines.scss
+++ b/app/assets/stylesheets/pages/pipelines.scss
@@ -98,6 +98,10 @@
         }
       }
 
+      .btn .text-center {
+        display: inline;
+      }
+
       .tooltip {
         white-space: nowrap;
       }
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 @@ def mark_as_spam
 
   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 @@ def spammable_params
     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/finders/groups_finder.rb b/app/finders/groups_finder.rb
index f68610e197cc6d6545520940bace1bc313bcae3a..03d145c4ccd22bc22e79e41fc69495fb01216e3a 100644
--- a/app/finders/groups_finder.rb
+++ b/app/finders/groups_finder.rb
@@ -16,7 +16,10 @@ def execute
   def all_groups
     groups = []
 
-    groups << current_user.authorized_groups if current_user
+    if current_user
+      groups << Group.member_self_and_descendants(current_user.id)
+      groups << current_user.groups_through_project_authorizations
+    end
     groups << Group.unscoped.public_to_user(current_user)
 
     groups
diff --git a/app/helpers/labels_helper.rb b/app/helpers/labels_helper.rb
index e5b1e6e8bc70d0eba506444c26e9af2ad717affc..4e6e68059204c1a9f05f7b7a27d91315ec2d32d0 100644
--- a/app/helpers/labels_helper.rb
+++ b/app/helpers/labels_helper.rb
@@ -69,13 +69,12 @@ def destroy_label_path(label)
   end
 
   def render_colored_label(label, label_suffix = '', tooltip: true)
-    label_color = label.color || Label::DEFAULT_COLOR
-    text_color = text_color_for_bg(label_color)
+    text_color = text_color_for_bg(label.color)
 
     # Intentionally not using content_tag here so that this method can be called
     # by LabelReferenceFilter
     span = %(<span class="label color-label #{"has-tooltip" if tooltip}" ) +
-      %(style="background-color: #{label_color}; color: #{text_color}" ) +
+      %(style="background-color: #{label.color}; color: #{text_color}" ) +
       %(title="#{escape_once(label.description)}" data-container="body">) +
       %(#{escape_once(label.name)}#{label_suffix}</span>)
 
diff --git a/app/helpers/submodule_helper.rb b/app/helpers/submodule_helper.rb
index b739554a7a42c5099717cb74c75c5af2f3dbd087..6d3b1ca435882b5312914201e46513a6a69f992e 100644
--- a/app/helpers/submodule_helper.rb
+++ b/app/helpers/submodule_helper.rb
@@ -9,6 +9,7 @@ def submodule_links(submodule_item, ref = nil, repository = @repository)
 
     if url =~ /([^\/:]+)\/([^\/]+(?:\.git)?)\Z/
       namespace, project = $1, $2
+      project.rstrip!
       project.sub!(/\.git\z/, '')
 
       if self_url?(url, namespace, project)
diff --git a/app/models/ci/pipeline_schedule.rb b/app/models/ci/pipeline_schedule.rb
index 6d7cc83971e002df176b452daa20b86c3891f5bb..07213ca608a956b70ee44e9ede192975f93de559 100644
--- a/app/models/ci/pipeline_schedule.rb
+++ b/app/models/ci/pipeline_schedule.rb
@@ -10,9 +10,9 @@ class PipelineSchedule < ActiveRecord::Base
     has_one :last_pipeline, -> { order(id: :desc) }, class_name: 'Ci::Pipeline'
     has_many :pipelines
 
-    validates :cron, unless: :importing_or_inactive?, cron: true, presence: { unless: :importing_or_inactive? }
-    validates :cron_timezone, cron_timezone: true, presence: { unless: :importing_or_inactive? }
-    validates :ref, presence: { unless: :importing_or_inactive? }
+    validates :cron, unless: :importing?, cron: true, presence: { unless: :importing? }
+    validates :cron_timezone, cron_timezone: true, presence: { unless: :importing? }
+    validates :ref, presence: { unless: :importing? }
     validates :description, presence: true
 
     before_save :set_next_run_at
@@ -28,8 +28,12 @@ def inactive?
       !active?
     end
 
-    def importing_or_inactive?
-      importing? || inactive?
+    def deactivate!
+      update_attribute(:active, false)
+    end
+
+    def runnable_by_owner?
+      Ability.allowed?(owner, :create_pipeline, project)
     end
 
     def set_next_run_at
diff --git a/app/models/label.rb b/app/models/label.rb
index ddddb6bdf8fa60471c77b295b8bd8755cdfc8dd0..074239702f82b9ea4a1a51493f4abc615bde93f1 100644
--- a/app/models/label.rb
+++ b/app/models/label.rb
@@ -133,6 +133,10 @@ def template?
     template
   end
 
+  def color
+    super || DEFAULT_COLOR
+  end
+
   def text_color
     LabelsHelper.text_color_for_bg(self.color)
   end
diff --git a/app/models/milestone.rb b/app/models/milestone.rb
index 7c5ceb28d1cd10559c56346121e204a4f5a15581..c6d0b0c70f0c7aa5934a8d783db7fcee216c0b08 100644
--- a/app/models/milestone.rb
+++ b/app/models/milestone.rb
@@ -109,7 +109,7 @@ def self.upcoming_ids_by_projects(projects)
   end
 
   def participants
-    User.joins(assigned_issues: :milestone).where("milestones.id = ?", id)
+    User.joins(assigned_issues: :milestone).where("milestones.id = ?", id).uniq
   end
 
   def self.sort(method)
diff --git a/app/models/repository.rb b/app/models/repository.rb
index d4124465e9aec971382965c7a4484a67a4895cdf..6baafc9137365e567336d861745298471ab347dc 100644
--- a/app/models/repository.rb
+++ b/app/models/repository.rb
@@ -1027,6 +1027,8 @@ def merge_base(first_commit_id, second_commit_id)
   end
 
   def is_ancestor?(ancestor_id, descendant_id)
+    return false if ancestor_id.nil? || descendant_id.nil?
+    
     Gitlab::GitalyClient.migrate(:is_ancestor) do |is_enabled|
       if is_enabled
         raw_repository.is_ancestor?(ancestor_id, descendant_id)
diff --git a/app/models/user.rb b/app/models/user.rb
index 2c7a6499abd89e6ecec5e0f6434671c6edc3a12c..0e6593781ea8d8c33f9604521359af897d79ee04 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -519,6 +519,17 @@ def authorized_groups
     Group.where("namespaces.id IN (#{union.to_sql})")
   end
 
+  def groups_through_project_authorizations
+    projects = Project.joins(:project_authorizations).
+                 where('project_authorizations.user_id = ?', id ).
+                 joins(:route).
+                 select('routes.path AS full_path')
+
+    Group.joins(:route).
+      joins("INNER JOIN (#{projects.to_sql}) project_paths
+            ON project_paths.full_path LIKE CONCAT(routes_namespaces.path, '/%')")
+  end
+
   def nested_groups
     Group.member_descendants(id)
   end
@@ -934,13 +945,13 @@ def global_notification_setting
   end
 
   def assigned_open_merge_requests_count(force: false)
-    Rails.cache.fetch(['users', id, 'assigned_open_merge_requests_count'], force: force) do
+    Rails.cache.fetch(['users', id, 'assigned_open_merge_requests_count'], force: force, expires_in: 20.minutes) do
       MergeRequestsFinder.new(self, assignee_id: self.id, state: 'opened').execute.count
     end
   end
 
   def assigned_open_issues_count(force: false)
-    Rails.cache.fetch(['users', id, 'assigned_open_issues_count'], force: force) do
+    Rails.cache.fetch(['users', id, 'assigned_open_issues_count'], force: force, expires_in: 20.minutes) do
       IssuesFinder.new(self, assignee_id: self.id, state: 'opened').execute.count
     end
   end
@@ -951,10 +962,18 @@ def update_cache_counts
   end
 
   def invalidate_cache_counts
-    Rails.cache.delete(['users', id, 'assigned_open_merge_requests_count'])
+    invalidate_issue_cache_counts
+    invalidate_merge_request_cache_counts
+  end
+
+  def invalidate_issue_cache_counts
     Rails.cache.delete(['users', id, 'assigned_open_issues_count'])
   end
 
+  def invalidate_merge_request_cache_counts
+    Rails.cache.delete(['users', id, 'assigned_open_merge_requests_count'])
+  end
+
   def todos_done_count(force: false)
     Rails.cache.fetch(['users', id, 'todos_done_count'], force: force) do
       TodosFinder.new(self, state: :done).execute.count
diff --git a/app/policies/ci/build_policy.rb b/app/policies/ci/build_policy.rb
index d4af4490608622bf64494f489249d5f21bb858ee..2d7405dc2403446061246f110fceeaa71e98b831 100644
--- a/app/policies/ci/build_policy.rb
+++ b/app/policies/ci/build_policy.rb
@@ -23,7 +23,7 @@ def protected_action?
 
       !::Gitlab::UserAccess
         .new(user, project: build.project)
-        .can_push_to_branch?(build.ref)
+        .can_merge_to_branch?(build.ref)
     end
   end
 end
diff --git a/app/serializers/merge_request_entity.rb b/app/serializers/merge_request_entity.rb
index 054d681f131de57c7445729659deb44fd0ec6935..8a6ef7a720e92d59fab1e52615c109439f22bdd1 100644
--- a/app/serializers/merge_request_entity.rb
+++ b/app/serializers/merge_request_entity.rb
@@ -58,6 +58,7 @@ class MergeRequestEntity < IssuableEntity
   expose :commits_count
   expose :cannot_be_merged?, as: :has_conflicts
   expose :can_be_merged?, as: :can_be_merged
+  expose :remove_source_branch?, as: :remove_source_branch
 
   expose :project_archived do |merge_request|
     merge_request.project.archived?
diff --git a/app/services/issuable_base_service.rb b/app/services/issuable_base_service.rb
index f70edb982cd2437038e1955c320e019583f98f8b..d1b202930e652714dec01f7d147831cce32cfc8b 100644
--- a/app/services/issuable_base_service.rb
+++ b/app/services/issuable_base_service.rb
@@ -178,7 +178,7 @@ def create(issuable)
       after_create(issuable)
       issuable.create_cross_references!(current_user)
       execute_hooks(issuable)
-      issuable.assignees.each(&:invalidate_cache_counts)
+      invalidate_cache_counts(issuable.assignees, issuable)
     end
 
     issuable
@@ -239,6 +239,7 @@ def update(issuable)
           new_assignees = issuable.assignees.to_a
           affected_assignees = (old_assignees + new_assignees) - (old_assignees & new_assignees)
           affected_assignees.compact.each(&:invalidate_cache_counts)
+          invalidate_cache_counts(affected_assignees.compact, issuable)
         end
 
         after_update(issuable)
@@ -331,4 +332,10 @@ def handle_common_system_notes(issuable, old_labels: [])
 
     create_labels_note(issuable, old_labels) if issuable.labels != old_labels
   end
+
+  def invalidate_cache_counts(users, issuable)
+    users.each do |user|
+      user.public_send("invalidate_#{issuable.model_name.singular}_cache_counts")
+    end
+  end
 end
diff --git a/app/services/issues/close_service.rb b/app/services/issues/close_service.rb
index f1030912c68750e348df38be54803eaa73735963..85c616ca576d56cf63961e5c37efe66f9d94c0bb 100644
--- a/app/services/issues/close_service.rb
+++ b/app/services/issues/close_service.rb
@@ -28,6 +28,7 @@ def close_issue(issue, commit: nil, notifications: true, system_note: true)
         notification_service.close_issue(issue, current_user) if notifications
         todo_service.close_issue(issue, current_user)
         execute_hooks(issue, 'close')
+        invalidate_cache_counts(issue.assignees, issue)
       end
 
       issue
diff --git a/app/services/issues/reopen_service.rb b/app/services/issues/reopen_service.rb
index 40fbe354492842ecc67d26710c3b717f0e7c946c..80ea6312768bc9c662806938a0d14d702fc19004 100644
--- a/app/services/issues/reopen_service.rb
+++ b/app/services/issues/reopen_service.rb
@@ -8,6 +8,7 @@ def execute(issue)
         create_note(issue)
         notification_service.reopen_issue(issue, current_user)
         execute_hooks(issue, 'reopen')
+        invalidate_cache_counts(issue.assignees, issue)
       end
 
       issue
diff --git a/app/services/merge_requests/close_service.rb b/app/services/merge_requests/close_service.rb
index f2053bda83aadbb4dbcb8766999f404d02b6c7bc..2ffc989ed71c984d40445c408caf374c8b2ca465 100644
--- a/app/services/merge_requests/close_service.rb
+++ b/app/services/merge_requests/close_service.rb
@@ -13,6 +13,7 @@ def execute(merge_request, commit = nil)
         notification_service.close_mr(merge_request, current_user)
         todo_service.close_merge_request(merge_request, current_user)
         execute_hooks(merge_request, 'close')
+        invalidate_cache_counts(merge_request.assignees, merge_request)
       end
 
       merge_request
diff --git a/app/services/merge_requests/post_merge_service.rb b/app/services/merge_requests/post_merge_service.rb
index e8fb1b597527cb1088988cbefff626bec2cdff94..f0d998731d75be214ac3eb1dc098d35857e5fc04 100644
--- a/app/services/merge_requests/post_merge_service.rb
+++ b/app/services/merge_requests/post_merge_service.rb
@@ -13,6 +13,7 @@ def execute(merge_request)
       create_note(merge_request)
       notification_service.merge_mr(merge_request, current_user)
       execute_hooks(merge_request, 'merge')
+      invalidate_cache_counts(merge_request.assignees, merge_request)
     end
 
     private
diff --git a/app/services/merge_requests/reopen_service.rb b/app/services/merge_requests/reopen_service.rb
index fadcce5d9b6e13f27e68d8a3433cc471fc1a6eea..b016e5235cd079e0921e9c7220afe146bf96e113 100644
--- a/app/services/merge_requests/reopen_service.rb
+++ b/app/services/merge_requests/reopen_service.rb
@@ -10,6 +10,7 @@ def execute(merge_request)
         execute_hooks(merge_request, 'reopen')
         merge_request.reload_diff
         merge_request.mark_as_unchecked
+        invalidate_cache_counts(merge_request.assignees, merge_request)
       end
 
       merge_request
diff --git a/app/services/slash_commands/interpret_service.rb b/app/services/slash_commands/interpret_service.rb
index 2c239e123ab191cba71f0bd54c6dc58879b4dfa2..ccee5e4b6ce341abf6bfb4d54a009967e4e5dc82 100644
--- a/app/services/slash_commands/interpret_service.rb
+++ b/app/services/slash_commands/interpret_service.rb
@@ -92,26 +92,20 @@ def extractor
 
     desc 'Assign'
     explanation do |users|
-      "Assigns #{users.map(&:to_reference).to_sentence}." if users.any?
+      "Assigns #{users.first.to_reference}." if users.any?
     end
     params '@user'
     condition do
       current_user.can?(:"admin_#{issuable.to_ability_name}", project)
     end
     parse_params do |assignee_param|
-      users = extract_references(assignee_param, :user)
-
-      if users.empty?
-        users = User.where(username: assignee_param.split(' ').map(&:strip))
-      end
-
-      users
+      extract_users(assignee_param)
     end
     command :assign do |users|
       next if users.empty?
 
       if issuable.is_a?(Issue)
-        @updates[:assignee_ids] = users.map(&:id)
+        @updates[:assignee_ids] = [users.last.id]
       else
         @updates[:assignee_id] = users.last.id
       end
@@ -487,6 +481,18 @@ def extractor
       end
     end
 
+    def extract_users(params)
+      return [] if params.nil?
+
+      users = extract_references(params, :user)
+
+      if users.empty?
+        users = User.where(username: params.split(' ').map(&:strip))
+      end
+
+      users
+    end
+
     def find_labels(labels_param)
       extract_references(labels_param, :label) |
         LabelsFinder.new(current_user, project_id: project.id, name: labels_param.split).execute
diff --git a/app/uploaders/lfs_object_uploader.rb b/app/uploaders/lfs_object_uploader.rb
index 95a891111e117e50e0526130aaca935d3d32b826..02589959c2f2ff62066b954c5c6022f25ad820f2 100644
--- a/app/uploaders/lfs_object_uploader.rb
+++ b/app/uploaders/lfs_object_uploader.rb
@@ -12,4 +12,20 @@ def cache_dir
   def filename
     model.oid[4..-1]
   end
+
+  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/projects/deployments/_actions.haml b/app/views/projects/deployments/_actions.haml
index 506246f2ee60826b3b79693e9c8bb8a97a271330..e2baaa625aef4dcd4beacd333affc4c1deda5bc9 100644
--- a/app/views/projects/deployments/_actions.haml
+++ b/app/views/projects/deployments/_actions.haml
@@ -8,6 +8,7 @@
           = icon('caret-down')
         %ul.dropdown-menu.dropdown-menu-align-right
           - actions.each do |action|
+            - next unless can?(current_user, :update_build, action)
             %li
               = link_to [:play, @project.namespace.becomes(Namespace), @project, action], method: :post, rel: 'nofollow' do
                 = custom_icon('icon_play')
diff --git a/app/views/projects/environments/show.html.haml b/app/views/projects/environments/show.html.haml
index 7315e6710569b103e6f72180c751f557d374d6c3..9e221240cf2141d74a8e56023bee0e59b3c74690 100644
--- a/app/views/projects/environments/show.html.haml
+++ b/app/views/projects/environments/show.html.haml
@@ -13,7 +13,7 @@
         = render 'projects/environments/metrics_button', environment: @environment
         - if can?(current_user, :update_environment, @environment)
           = link_to 'Edit', edit_namespace_project_environment_path(@project.namespace, @project, @environment), class: 'btn'
-        - if can?(current_user, :create_deployment, @environment) && @environment.can_stop?
+        - if can?(current_user, :stop_environment, @environment)
           = link_to 'Stop', stop_namespace_project_environment_path(@project.namespace, @project, @environment), data: { confirm: 'Are you sure you want to stop this environment?' }, class: 'btn btn-danger', method: :post
 
   .environments-container
diff --git a/app/views/projects/pipeline_schedules/_pipeline_schedule.html.haml b/app/views/projects/pipeline_schedules/_pipeline_schedule.html.haml
index 075ecee434364b2c254575b14fa51adb49fd8388..9e4f96839f4909c112b00903459bdb9a92ba9a45 100644
--- a/app/views/projects/pipeline_schedules/_pipeline_schedule.html.haml
+++ b/app/views/projects/pipeline_schedules/_pipeline_schedule.html.haml
@@ -4,7 +4,9 @@
       = pipeline_schedule.description
     %td.branch-name-cell
       = icon('code-fork')
-      = link_to pipeline_schedule.ref, namespace_project_commits_path(@project.namespace, @project, pipeline_schedule.ref), class: "branch-name"
+      - if pipeline_schedule.ref
+        = link_to pipeline_schedule.ref, namespace_project_commits_path(@project.namespace, @project, pipeline_schedule.ref), class: "branch-name"
+
     %td
       - if pipeline_schedule.last_pipeline
         .status-icon-container{ class: "ci-status-icon-#{pipeline_schedule.last_pipeline.status}" }
@@ -15,7 +17,7 @@
         None
     %td.next-run-cell
       - if pipeline_schedule.active?
-        = time_ago_with_tooltip(pipeline_schedule.next_run_at)
+        = time_ago_with_tooltip(pipeline_schedule.real_next_run)
       - else
         Inactive
     %td
diff --git a/app/views/shared/issuable/form/_merge_params.html.haml b/app/views/shared/issuable/form/_merge_params.html.haml
index 41c09012e668cecc49f20efb6a5f1ba2ddfd6b5d..d40ab6b3dd4c25201bf34836b63f8a341d614b3e 100644
--- a/app/views/shared/issuable/form/_merge_params.html.haml
+++ b/app/views/shared/issuable/form/_merge_params.html.haml
@@ -3,14 +3,14 @@
 - return unless issuable.is_a?(MergeRequest)
 - return if issuable.closed_without_fork?
 
--# This check is duplicated below, to avoid conflicts with EE.
-- return unless issuable.can_remove_source_branch?(current_user)
-
 .form-group
   .col-sm-10.col-sm-offset-2
-    .checkbox
-      = label_tag 'merge_request[squash]' do
-        = hidden_field_tag 'merge_request[squash]', '0', id: nil
-        = check_box_tag 'merge_request[squash]', '1', issuable.squash
-        Squash commits when merge request is accepted.
-        = link_to 'About this feature', help_page_path('user/project/merge_requests/squash_and_merge')
+    - if issuable.can_remove_source_branch?(current_user)
+      .checkbox
+        - initial_checkbox_value = issuable.merge_params.key?('force_remove_source_branch') ? issuable.force_remove_source_branch? : true
+        = label_tag 'merge_request[force_remove_source_branch]' do
+          = hidden_field_tag 'merge_request[force_remove_source_branch]', '0', id: nil
+          = check_box_tag 'merge_request[force_remove_source_branch]', '1', initial_checkbox_value
+          Remove source branch when merge request is accepted.
+
+= render 'shared/issuable/form/ee/squash_merge_param', issuable: issuable
diff --git a/app/views/shared/issuable/form/ee/_squash_merge_param.html.haml b/app/views/shared/issuable/form/ee/_squash_merge_param.html.haml
new file mode 100644
index 0000000000000000000000000000000000000000..7cce11752df55fd8abc193ea8008ea4066d4e951
--- /dev/null
+++ b/app/views/shared/issuable/form/ee/_squash_merge_param.html.haml
@@ -0,0 +1,8 @@
+.form-group
+  .col-sm-10.col-sm-offset-2
+    .checkbox
+      = label_tag 'merge_request[squash]' do
+        = hidden_field_tag 'merge_request[squash]', '0', id: nil
+        = check_box_tag 'merge_request[squash]', '1', issuable.squash
+        Squash commits when merge request is accepted.
+        = link_to 'About this feature', help_page_path('user/project/merge_requests/squash_and_merge')
diff --git a/app/views/shared/notes/_note.html.haml b/app/views/shared/notes/_note.html.haml
index a7bf610b9c709e41089766c0b3094a9c7569ccea..1e34b7c1e76acd2bef84930de58a4e8a2403ff5e 100644
--- a/app/views/shared/notes/_note.html.haml
+++ b/app/views/shared/notes/_note.html.haml
@@ -18,7 +18,7 @@
       .note-header
         .note-header-info
           %a{ href: user_path(note.author) }
-            %span.hidden-xs
+            %span.note-header-author-name
               = sanitize(note.author.name)
             %span.note-headline-light
               = note.author.to_reference
diff --git a/changelogs/unreleased/31556-ci-coverage-paralel-rspec.yml b/changelogs/unreleased/31556-ci-coverage-paralel-rspec.yml
new file mode 100644
index 0000000000000000000000000000000000000000..4137050a0771eaca8375ebbc4d063f11fbe2e23a
--- /dev/null
+++ b/changelogs/unreleased/31556-ci-coverage-paralel-rspec.yml
@@ -0,0 +1,4 @@
+---
+title: Fix the last coverage in trace log should be extracted
+merge_request: 11128
+author: dosuken123
diff --git a/changelogs/unreleased/32790-pipeline_schedules-pages-throwing-error-500.yml b/changelogs/unreleased/32790-pipeline_schedules-pages-throwing-error-500.yml
new file mode 100644
index 0000000000000000000000000000000000000000..a58f3a7429e36e412ba74c9169a4166470a7f74c
--- /dev/null
+++ b/changelogs/unreleased/32790-pipeline_schedules-pages-throwing-error-500.yml
@@ -0,0 +1,4 @@
+---
+title: Fix pipeline_schedules pages throwing error 500
+merge_request: 11706
+author: dosuken123
diff --git a/changelogs/unreleased/32995-issue-contents-dynamically-replaced-with-stale-version-after-saving-or-refreshing-relative-external_url-only.yml b/changelogs/unreleased/32995-issue-contents-dynamically-replaced-with-stale-version-after-saving-or-refreshing-relative-external_url-only.yml
new file mode 100644
index 0000000000000000000000000000000000000000..5cd36a4e3e2ab675c992b9265146369221f352bf
--- /dev/null
+++ b/changelogs/unreleased/32995-issue-contents-dynamically-replaced-with-stale-version-after-saving-or-refreshing-relative-external_url-only.yml
@@ -0,0 +1,4 @@
+---
+title: Fix incorrect ETag cache key when relative instance URL is used
+merge_request: 11964
+author:
diff --git a/changelogs/unreleased/33048-markdown-rendering-of-md-files-has-ceased-to-display-latex-equations.yml b/changelogs/unreleased/33048-markdown-rendering-of-md-files-has-ceased-to-display-latex-equations.yml
new file mode 100644
index 0000000000000000000000000000000000000000..5648e013e75e72db4031a62ccf9360ec673d1860
--- /dev/null
+++ b/changelogs/unreleased/33048-markdown-rendering-of-md-files-has-ceased-to-display-latex-equations.yml
@@ -0,0 +1,4 @@
+---
+title: Fix math rendering on blob pages
+merge_request:
+author:
diff --git a/changelogs/unreleased/counters_cache_invalidation.yml b/changelogs/unreleased/counters_cache_invalidation.yml
new file mode 100644
index 0000000000000000000000000000000000000000..1e78765ec101378ac2a9e3ef5b921215ccec5172
--- /dev/null
+++ b/changelogs/unreleased/counters_cache_invalidation.yml
@@ -0,0 +1,4 @@
+---
+title: Invalidate cache for issue and MR counters more granularly
+merge_request:
+author:
diff --git a/changelogs/unreleased/fix-backup-restore-resume.yml b/changelogs/unreleased/fix-backup-restore-resume.yml
new file mode 100644
index 0000000000000000000000000000000000000000..b7dfd451f5dea3ce65ad986e9210ca5d48bb5cb4
--- /dev/null
+++ b/changelogs/unreleased/fix-backup-restore-resume.yml
@@ -0,0 +1,4 @@
+---
+title: Make backup task to continue on corrupt repositories
+merge_request: 11962
+author:
diff --git a/changelogs/unreleased/fix-gb-use-merge-ability-for-protected-manual-actions.yml b/changelogs/unreleased/fix-gb-use-merge-ability-for-protected-manual-actions.yml
new file mode 100644
index 0000000000000000000000000000000000000000..43c18502cd6e60734ce8622fa1537055860e0655
--- /dev/null
+++ b/changelogs/unreleased/fix-gb-use-merge-ability-for-protected-manual-actions.yml
@@ -0,0 +1,4 @@
+---
+title: Respect merge, instead of push, permissions for protected actions
+merge_request: 11648
+author:
diff --git a/changelogs/unreleased/sh-fix-lfs-from-moving-across-filesystems.yml b/changelogs/unreleased/sh-fix-lfs-from-moving-across-filesystems.yml
new file mode 100644
index 0000000000000000000000000000000000000000..161bce456017baff7605fd2e011a9614b777056e
--- /dev/null
+++ b/changelogs/unreleased/sh-fix-lfs-from-moving-across-filesystems.yml
@@ -0,0 +1,4 @@
+---
+title: Fix LFS timeouts when trying to save large files
+merge_request:
+author:
diff --git a/changelogs/unreleased/sh-fix-submodules-trailing-spaces.yml b/changelogs/unreleased/sh-fix-submodules-trailing-spaces.yml
new file mode 100644
index 0000000000000000000000000000000000000000..d633995d4676eb0706fe6c63654d1e962ef77783
--- /dev/null
+++ b/changelogs/unreleased/sh-fix-submodules-trailing-spaces.yml
@@ -0,0 +1,4 @@
+---
+title: Strip trailing whitespaces in submodule URLs
+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-drop-fk-if-exists.yml b/changelogs/unreleased/zj-drop-fk-if-exists.yml
new file mode 100644
index 0000000000000000000000000000000000000000..237ba936de98c2c13a4bdcac59f31e6541e5086d
--- /dev/null
+++ b/changelogs/unreleased/zj-drop-fk-if-exists.yml
@@ -0,0 +1,4 @@
+---
+title: Remove foreigh key on ci_trigger_schedules only if it exists
+merge_request:
+author:
diff --git a/db/migrate/20170425112628_remove_foreigh_key_ci_trigger_schedules.rb b/db/migrate/20170425112628_remove_foreigh_key_ci_trigger_schedules.rb
index 6116ca59ee4d10e1453c59963951997c75455ab8..1587eee06ae2f8f38bc7c89dc0bdcb02cb8fa068 100644
--- a/db/migrate/20170425112628_remove_foreigh_key_ci_trigger_schedules.rb
+++ b/db/migrate/20170425112628_remove_foreigh_key_ci_trigger_schedules.rb
@@ -4,10 +4,20 @@ class RemoveForeighKeyCiTriggerSchedules < ActiveRecord::Migration
   DOWNTIME = false
 
   def up
-    remove_foreign_key :ci_trigger_schedules, column: :trigger_id
+    if fk_on_trigger_schedules?
+      remove_foreign_key :ci_trigger_schedules, column: :trigger_id
+    end
   end
 
   def down
     # no op, the foreign key should not have been here
   end
+
+  private
+
+  # Not made more generic and lifted to the helpers as Rails 5 will provide
+  # such an API
+  def fk_on_trigger_schedules?
+    connection.foreign_keys(:ci_trigger_schedules).include?("ci_triggers")
+  end
 end
diff --git a/doc/ci/yaml/README.md b/doc/ci/yaml/README.md
index e542b1119eab18309f9ee1f46775e50b224765ac..0a0be717fa8308b93077153b4d3063db7712b8f3 100644
--- a/doc/ci/yaml/README.md
+++ b/doc/ci/yaml/README.md
@@ -587,7 +587,7 @@ Optional manual actions have `allow_failure: true` set by default.
 **Manual actions are considered to be write actions, so permissions for
 protected branches are used when user wants to trigger an action. In other
 words, in order to trigger a manual action assigned to a branch that the
-pipeline is running for, user needs to have ability to push to this branch.**
+pipeline is running for, user needs to have ability to merge to this branch.**
 
 ### environment
 
diff --git a/features/project/merge_requests/accept.feature b/features/project/merge_requests/accept.feature
index c45ed9ea68b3d378a1d1a8deafce7b62da5d5a73..2ab1c19f4527b82c96611b6c19f32a3a8d5199fa 100644
--- a/features/project/merge_requests/accept.feature
+++ b/features/project/merge_requests/accept.feature
@@ -7,6 +7,7 @@ Feature: Project Merge Requests Acceptance
   @javascript
   Scenario: Accepting the Merge Request and removing the source branch
     Given I am on the Merge Request detail page
+    When I check the "Remove source branch" option
     And I click on Accept Merge Request
     Then I should see merge request merged
     And I should not see the Remove Source Branch button
@@ -14,6 +15,7 @@ Feature: Project Merge Requests Acceptance
   @javascript
   Scenario: Accepting the Merge Request when URL has an anchor
     Given I am on the Merge Request detail with note anchor page
+    When I check the "Remove source branch" option
     And I click on Accept Merge Request
     Then I should see merge request merged
     And I should not see the Remove Source Branch button
@@ -21,7 +23,6 @@ Feature: Project Merge Requests Acceptance
   @javascript
   Scenario: Accepting the Merge Request without removing the source branch
     Given I am on the Merge Request detail page
-    When I click on "Remove source branch" option
     When I click on Accept Merge Request
     Then I should see merge request merged
     And I should see the Remove Source Branch button
diff --git a/features/steps/project/merge_requests/acceptance.rb b/features/steps/project/merge_requests/acceptance.rb
index 3c976f675a26e0dce00a6b6c83343a64ee3126d8..cdaa849308c126541a7080443f15fe1e43e740e2 100644
--- a/features/steps/project/merge_requests/acceptance.rb
+++ b/features/steps/project/merge_requests/acceptance.rb
@@ -11,10 +11,14 @@ class Spinach::Features::ProjectMergeRequestsAcceptance < Spinach::FeatureSteps
     visit merge_request_path(@merge_request, anchor: 'note_123')
   end
 
-  step 'I click on "Remove source branch" option' do
+  step 'I uncheck the "Remove source branch" option' do
     uncheck('Remove source branch')
   end
 
+  step 'I check the "Remove source branch" option' do
+    check('Remove source branch')
+  end
+
   step 'I click on Accept Merge Request' do
     click_button('Merge')
   end
diff --git a/lib/backup/repository.rb b/lib/backup/repository.rb
index 6b29600a75133a3d350c7028e18161f6853b6eaf..a1685c779167bf288cc055da796e0ec030e996c8 100644
--- a/lib/backup/repository.rb
+++ b/lib/backup/repository.rb
@@ -7,15 +7,15 @@ def dump
       prepare
 
       Project.find_each(batch_size: 1000) do |project|
-        $progress.print " * #{project.path_with_namespace} ... "
+        progress.print " * #{project.path_with_namespace} ... "
         path_to_project_repo = path_to_repo(project)
         path_to_project_bundle = path_to_bundle(project)
 
         # Create namespace dir if missing
         FileUtils.mkdir_p(File.join(backup_repos_path, project.namespace.full_path)) if project.namespace
 
-        if project.empty_repo?
-          $progress.puts "[SKIPPED]".color(:cyan)
+        if empty_repo?(project)
+          progress.puts "[SKIPPED]".color(:cyan)
         else
           in_path(path_to_project_repo) do |dir|
             FileUtils.mkdir_p(path_to_tars(project))
@@ -23,10 +23,7 @@ def dump
             output, status = Gitlab::Popen.popen(cmd)
 
             unless status.zero?
-              puts "[FAILED]".color(:red)
-              puts "failed: #{cmd.join(' ')}"
-              puts output
-              abort 'Backup failed'
+              progress_warn(project, cmd.join(' '), output)
             end
           end
 
@@ -34,12 +31,9 @@ def dump
           output, status = Gitlab::Popen.popen(cmd)
 
           if status.zero?
-            $progress.puts "[DONE]".color(:green)
+            progress.puts "[DONE]".color(:green)
           else
-            puts "[FAILED]".color(:red)
-            puts "failed: #{cmd.join(' ')}"
-            puts output
-            abort 'Backup failed'
+            progress_warn(project, cmd.join(' '), output)
           end
         end
 
@@ -48,19 +42,16 @@ def dump
         path_to_wiki_bundle = path_to_bundle(wiki)
 
         if File.exist?(path_to_wiki_repo)
-          $progress.print " * #{wiki.path_with_namespace} ... "
-          if wiki.repository.empty?
-            $progress.puts " [SKIPPED]".color(:cyan)
+          progress.print " * #{wiki.path_with_namespace} ... "
+          if empty_repo?(wiki)
+            progress.puts " [SKIPPED]".color(:cyan)
           else
             cmd = %W(#{Gitlab.config.git.bin_path} --git-dir=#{path_to_wiki_repo} bundle create #{path_to_wiki_bundle} --all)
             output, status = Gitlab::Popen.popen(cmd)
             if status.zero?
-              $progress.puts " [DONE]".color(:green)
+              progress.puts " [DONE]".color(:green)
             else
-              puts " [FAILED]".color(:red)
-              puts "failed: #{cmd.join(' ')}"
-              puts output
-              abort 'Backup failed'
+              progress_warn(wiki, cmd.join(' '), output)
             end
           end
         end
@@ -80,7 +71,7 @@ def restore
       end
 
       Project.find_each(batch_size: 1000) do |project|
-        $progress.print " * #{project.path_with_namespace} ... "
+        progress.print " * #{project.path_with_namespace} ... "
         path_to_project_repo = path_to_repo(project)
         path_to_project_bundle = path_to_bundle(project)
 
@@ -94,12 +85,9 @@ def restore
 
         output, status = Gitlab::Popen.popen(cmd)
         if status.zero?
-          $progress.puts "[DONE]".color(:green)
+          progress.puts "[DONE]".color(:green)
         else
-          puts "[FAILED]".color(:red)
-          puts "failed: #{cmd.join(' ')}"
-          puts output
-          abort 'Restore failed'
+          progress_warn(project, cmd.join(' '), output)
         end
 
         in_path(path_to_tars(project)) do |dir|
@@ -107,10 +95,7 @@ def restore
 
           output, status = Gitlab::Popen.popen(cmd)
           unless status.zero?
-            puts "[FAILED]".color(:red)
-            puts "failed: #{cmd.join(' ')}"
-            puts output
-            abort 'Restore failed'
+            progress_warn(project, cmd.join(' '), output)
           end
         end
 
@@ -119,7 +104,7 @@ def restore
         path_to_wiki_bundle = path_to_bundle(wiki)
 
         if File.exist?(path_to_wiki_bundle)
-          $progress.print " * #{wiki.path_with_namespace} ... "
+          progress.print " * #{wiki.path_with_namespace} ... "
 
           # If a wiki bundle exists, first remove the empty repo
           # that was initialized with ProjectWiki.new() and then
@@ -129,22 +114,19 @@ def restore
 
           output, status = Gitlab::Popen.popen(cmd)
           if status.zero?
-            $progress.puts " [DONE]".color(:green)
+            progress.puts " [DONE]".color(:green)
           else
-            puts " [FAILED]".color(:red)
-            puts "failed: #{cmd.join(' ')}"
-            puts output
-            abort 'Restore failed'
+            progress_warn(project, cmd.join(' '), output)
           end
         end
       end
 
-      $progress.print 'Put GitLab hooks in repositories dirs'.color(:yellow)
+      progress.print 'Put GitLab hooks in repositories dirs'.color(:yellow)
       cmd = %W(#{Gitlab.config.gitlab_shell.path}/bin/create-hooks) + repository_storage_paths_args
 
       output, status = Gitlab::Popen.popen(cmd)
       if status.zero?
-        $progress.puts " [DONE]".color(:green)
+        progress.puts " [DONE]".color(:green)
       else
         puts " [FAILED]".color(:red)
         puts "failed: #{cmd}"
@@ -201,8 +183,25 @@ def silent
 
     private
 
+    def progress_warn(project, cmd, output)
+      progress.puts "[WARNING] Executing #{cmd}".color(:orange)
+      progress.puts "Ignoring error on #{project.path_with_namespace} - #{output}".color(:orange)
+    end
+
+    def empty_repo?(project_or_wiki)
+      project_or_wiki.repository.empty_repo?
+    rescue => e
+      progress.puts "Ignoring repository error and continuing backing up project: #{project_or_wiki.path_with_namespace} - #{e.message}".color(:orange)
+
+      false
+    end
+
     def repository_storage_paths_args
       Gitlab.config.repositories.storages.values.map { |rs| rs['path'] }
     end
+
+    def progress
+      $progress
+    end
   end
 end
diff --git a/lib/gitlab/ci/trace/stream.rb b/lib/gitlab/ci/trace/stream.rb
index fa462cbe095054545a1ccd7fde7de5cab1087727..c4c0623df6c559df9e761da009dee6dd77662a5d 100644
--- a/lib/gitlab/ci/trace/stream.rb
+++ b/lib/gitlab/ci/trace/stream.rb
@@ -73,7 +73,7 @@ def extract_coverage(regex)
 
           match = ""
 
-          stream.each_line do |line|
+          reverse_line do |line|
             matches = line.scan(regex)
             next unless matches.is_a?(Array)
             next if matches.empty?
@@ -86,34 +86,39 @@ def extract_coverage(regex)
           nil
         rescue
           # if bad regex or something goes wrong we dont want to interrupt transition
-          # so we just silentrly ignore error for now
+          # so we just silently ignore error for now
         end
 
         private
 
-        def read_last_lines(last_lines)
-          chunks = []
-          pos = lines = 0
-          max = stream.size
-
-          # We want an extra line to make sure fist line has full contents
-          while lines <= last_lines && pos < max
-            pos += BUFFER_SIZE
-
-            buf =
-              if pos <= max
-                stream.seek(-pos, IO::SEEK_END)
-                stream.read(BUFFER_SIZE)
-              else # Reached the head, read only left
-                stream.seek(0)
-                stream.read(BUFFER_SIZE - (pos - max))
-              end
-
-            lines += buf.count("\n")
-            chunks.unshift(buf)
+        def read_last_lines(limit)
+          to_enum(:reverse_line).first(limit).reverse.join
+        end
+
+        def reverse_line
+          stream.seek(0, IO::SEEK_END)
+          debris = ''
+
+          until (buf = read_backward(BUFFER_SIZE)).empty?
+            buf += debris
+            debris, *lines = buf.each_line.to_a
+            lines.reverse_each do |line|
+              yield(line.force_encoding('UTF-8'))
+            end
           end
 
-          chunks.join.lines.last(last_lines).join
+          yield(debris.force_encoding('UTF-8')) unless debris.empty?
+        end
+
+        def read_backward(length)
+          cur_offset = stream.tell
+          start = cur_offset - length
+          start = 0 if start < 0
+
+          stream.seek(start, IO::SEEK_SET)
+          stream.read(cur_offset - start).tap do
+            stream.seek(start, IO::SEEK_SET)
+          end
         end
       end
     end
diff --git a/lib/gitlab/etag_caching/middleware.rb b/lib/gitlab/etag_caching/middleware.rb
index 270d67dd50c2e25ed17fd6cf495f9ae8645ec61a..7f884183bb1b387c7ea506086660d510f0b31d08 100644
--- a/lib/gitlab/etag_caching/middleware.rb
+++ b/lib/gitlab/etag_caching/middleware.rb
@@ -6,12 +6,13 @@ def initialize(app)
       end
 
       def call(env)
-        route = Gitlab::EtagCaching::Router.match(env)
+        request = Rack::Request.new(env)
+        route = Gitlab::EtagCaching::Router.match(request)
         return @app.call(env) unless route
 
         track_event(:etag_caching_middleware_used, route)
 
-        etag, cached_value_present = get_etag(env)
+        etag, cached_value_present = get_etag(request)
         if_none_match = env['HTTP_IF_NONE_MATCH']
 
         if if_none_match == etag
@@ -27,8 +28,8 @@ def call(env)
 
       private
 
-      def get_etag(env)
-        cache_key = env['PATH_INFO']
+      def get_etag(request)
+        cache_key = request.path
         store = Gitlab::EtagCaching::Store.new
         current_value = store.get(cache_key)
         cached_value_present = current_value.present?
diff --git a/lib/gitlab/etag_caching/router.rb b/lib/gitlab/etag_caching/router.rb
index d74e31af5c676fe8f5d1d9f5fe27d399309ebfe2..597ccb58bfca55673a12fe94455b26a69fe17c1b 100644
--- a/lib/gitlab/etag_caching/router.rb
+++ b/lib/gitlab/etag_caching/router.rb
@@ -43,8 +43,8 @@ class Router
         ),
       ].freeze
 
-      def self.match(env)
-        ROUTES.find { |route| route.regexp.match(env['PATH_INFO']) }
+      def self.match(request)
+        ROUTES.find { |route| route.regexp.match(request.path_info) }
       end
     end
   end
diff --git a/lib/gitlab/gon_helper.rb b/lib/gitlab/gon_helper.rb
index 44684f36c451733a0f8ff8797d39344f423a1d19..e7935086b4bbef86b97a4bd6ad93ea5fb4934ace 100644
--- a/lib/gitlab/gon_helper.rb
+++ b/lib/gitlab/gon_helper.rb
@@ -1,3 +1,5 @@
+# rubocop:disable Metrics/AbcSize
+
 module Gitlab
   module GonHelper
     def add_gon_variables
@@ -14,6 +16,7 @@ def add_gon_variables
       gon.gitlab_url             = Gitlab.config.gitlab.url
       gon.test                   = Rails.env.test?
       gon.revision               = Gitlab::REVISION
+      gon.gitlab_logo            = ActionController::Base.helpers.asset_path('gitlab_logo.png')
 
       if current_user
         gon.current_user_id = current_user.id
diff --git a/lib/gitlab/slash_commands/command_definition.rb b/lib/gitlab/slash_commands/command_definition.rb
index 12a385f90fdf539da695e93b80367505803a4564..caab88560145fd8b71c1a978bf921cca4236773b 100644
--- a/lib/gitlab/slash_commands/command_definition.rb
+++ b/lib/gitlab/slash_commands/command_definition.rb
@@ -48,17 +48,23 @@ def execute(context, opts, arg)
       end
 
       def to_h(opts)
+        context = OpenStruct.new(opts)
+
         desc = description
         if desc.respond_to?(:call)
-          context = OpenStruct.new(opts)
           desc = context.instance_exec(&desc) rescue ''
         end
 
+        prms = params
+        if prms.respond_to?(:call)
+          prms = Array(context.instance_exec(&prms)) rescue params
+        end
+
         {
           name: name,
           aliases: aliases,
           description: desc,
-          params: params
+          params: prms
         }
       end
 
diff --git a/lib/gitlab/slash_commands/dsl.rb b/lib/gitlab/slash_commands/dsl.rb
index 614bafbe1b2139e3ea14fab0ad30ffcdb009c853..1b5b4566d8123c864b448e4dd38e5fd4f9301620 100644
--- a/lib/gitlab/slash_commands/dsl.rb
+++ b/lib/gitlab/slash_commands/dsl.rb
@@ -40,8 +40,8 @@ def desc(text = '', &block)
         #   command :command_key do |arguments|
         #     # Awesome code block
         #   end
-        def params(*params)
-          @params = params
+        def params(*params, &block)
+          @params = block_given? ? block : params
         end
 
         # Allows to give an explanation of what the command will do when
diff --git a/qa/Dockerfile b/qa/Dockerfile
index 72c82503542ad951325a141e713cebf9dfaddee7..9e2a74ef991ec3844011d7188bedc2a5d02daddc 100644
--- a/qa/Dockerfile
+++ b/qa/Dockerfile
@@ -1,10 +1,25 @@
 FROM ruby:2.3
 LABEL maintainer "Grzegorz Bizon <grzegorz@gitlab.com>"
 
-RUN sed -i "s/httpredir.debian.org/ftp.us.debian.org/" /etc/apt/sources.list && \
-    apt-get update && apt-get install -y --force-yes \
-      libqt5webkit5-dev qt5-qmake qt5-default build-essential xvfb git && \
-    apt-get clean
+##
+# Update APT sources and install some dependencies
+#
+RUN sed -i "s/httpredir.debian.org/ftp.us.debian.org/" /etc/apt/sources.list
+RUN apt-get update && apt-get install -y wget git unzip xvfb
+
+##
+# At this point Google Chrome Beta is 59 - first version with headless support
+#
+RUN wget -q https://dl.google.com/linux/direct/google-chrome-beta_current_amd64.deb
+RUN dpkg -i google-chrome-beta_current_amd64.deb; apt-get -fy install
+
+##
+# Install chromedriver to make it work with Selenium
+#
+RUN wget -q https://chromedriver.storage.googleapis.com/2.29/chromedriver_linux64.zip
+RUN unzip chromedriver_linux64.zip -d /usr/local/bin
+
+RUN apt-get clean
 
 WORKDIR /home/qa
 
diff --git a/qa/Gemfile b/qa/Gemfile
index 6bfe25ba4371b6372bb7743b47f7269fdc923c80..5d089a459346357e978500c46929713c073c5cad 100644
--- a/qa/Gemfile
+++ b/qa/Gemfile
@@ -2,6 +2,6 @@ source 'https://rubygems.org'
 
 gem 'capybara', '~> 2.12.1'
 gem 'capybara-screenshot', '~> 1.0.14'
-gem 'capybara-webkit', '~> 1.12.0'
 gem 'rake', '~> 12.0.0'
 gem 'rspec', '~> 3.5'
+gem 'selenium-webdriver', '~> 2.53'
diff --git a/qa/Gemfile.lock b/qa/Gemfile.lock
index 6de2abff1985fa60949cd279bcc08f5d6488f7b6..4dd71aa50106972e5013b2355f2ecfea7ecfe13b 100644
--- a/qa/Gemfile.lock
+++ b/qa/Gemfile.lock
@@ -16,7 +16,10 @@ GEM
     capybara-webkit (1.12.0)
       capybara (>= 2.3.0, < 2.13.0)
       json
+    childprocess (0.7.0)
+      ffi (~> 1.0, >= 1.0.11)
     diff-lcs (1.3)
+    ffi (1.9.18)
     json (2.0.3)
     launchy (2.4.3)
       addressable (~> 2.3)
@@ -44,6 +47,12 @@ GEM
       diff-lcs (>= 1.2.0, < 2.0)
       rspec-support (~> 3.5.0)
     rspec-support (3.5.0)
+    rubyzip (1.2.1)
+    selenium-webdriver (2.53.4)
+      childprocess (~> 0.5)
+      rubyzip (~> 1.0)
+      websocket (~> 1.0)
+    websocket (1.2.4)
     xpath (2.0.0)
       nokogiri (~> 1.3)
 
@@ -56,6 +65,7 @@ DEPENDENCIES
   capybara-webkit (~> 1.12.0)
   rake (~> 12.0.0)
   rspec (~> 3.5)
+  selenium-webdriver (~> 2.53)
 
 BUNDLED WITH
    1.14.6
diff --git a/qa/qa/specs/config.rb b/qa/qa/specs/config.rb
index d72187fcd34ff750f417e51d752a0949207a1e88..78a93828d36b17c706befdc0f6ab90740eb6976d 100644
--- a/qa/qa/specs/config.rb
+++ b/qa/qa/specs/config.rb
@@ -1,7 +1,7 @@
 require 'rspec/core'
 require 'capybara/rspec'
-require 'capybara-webkit'
 require 'capybara-screenshot/rspec'
+require 'selenium-webdriver'
 
 # rubocop:disable Metrics/MethodLength
 # rubocop:disable Metrics/LineLength
@@ -20,7 +20,6 @@ def perform
 
         configure_rspec!
         configure_capybara!
-        configure_webkit!
       end
 
       def configure_rspec!
@@ -43,9 +42,9 @@ def configure_rspec!
           config.order = :random
           Kernel.srand config.seed
 
-          config.before(:all) do
-            page.current_window.resize_to(1200, 1800)
-          end
+          # config.before(:all) do
+          #   page.current_window.resize_to(1200, 1800)
+          # end
 
           config.formatter = :documentation
           config.color = true
@@ -53,26 +52,28 @@ def configure_rspec!
       end
 
       def configure_capybara!
+        Capybara.register_driver :chrome do |app|
+          capabilities = Selenium::WebDriver::Remote::Capabilities.chrome(
+            'chromeOptions' => {
+              'binary' => '/opt/google/chrome-beta/google-chrome-beta',
+              'args' => %w[headless no-sandbox disable-gpu]
+            }
+          )
+
+          Capybara::Selenium::Driver
+            .new(app, browser: :chrome, desired_capabilities: capabilities)
+        end
+
         Capybara.configure do |config|
           config.app_host = @address
-          config.default_driver = :webkit
-          config.javascript_driver = :webkit
+          config.default_driver = :chrome
+          config.javascript_driver = :chrome
           config.default_max_wait_time = 4
 
           # https://github.com/mattheworiordan/capybara-screenshot/issues/164
           config.save_path = 'tmp'
         end
       end
-
-      def configure_webkit!
-        Capybara::Webkit.configure do |config|
-          config.allow_url(@address)
-          config.block_unknown_urls
-        end
-      rescue RuntimeError # rubocop:disable Lint/HandleExceptions
-        # TODO, Webkit is already configured, this make this
-        # configuration step idempotent, should be improved.
-      end
     end
   end
 end
diff --git a/qa/spec/spec_helper.rb b/qa/spec/spec_helper.rb
index c07a32346734b883388acd66fd0aff64a301d5a2..64d06ef65580fe31e139787931e7d8155b254502 100644
--- a/qa/spec/spec_helper.rb
+++ b/qa/spec/spec_helper.rb
@@ -12,7 +12,6 @@
   config.shared_context_metadata_behavior = :apply_to_host_groups
   config.disable_monkey_patching!
   config.expose_dsl_globally = true
-  config.warnings = true
   config.profile_examples = 10
   config.order = :random
   Kernel.srand config.seed
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/groups_controller_spec.rb b/spec/controllers/groups_controller_spec.rb
index da0a0c1e7f03d4c4a5f930e017b656c54e96640e..86333ae9bb7f2d3b707b0e505d754c027a567167 100644
--- a/spec/controllers/groups_controller_spec.rb
+++ b/spec/controllers/groups_controller_spec.rb
@@ -2,7 +2,7 @@
 
 describe GroupsController do
   let(:user) { create(:user) }
-  let(:group) { create(:group) }
+  let(:group) { create(:group, :public) }
   let(:project) { create(:empty_project, namespace: group) }
   let!(:group_member) { create(:group_member, group: group, user: user) }
 
@@ -35,14 +35,15 @@
         sign_in(user)
       end
 
-      it 'shows the public subgroups' do
+      it 'shows all subgroups' do
         get :subgroups, id: group.to_param
 
-        expect(assigns(:nested_groups)).to contain_exactly(public_subgroup)
+        expect(assigns(:nested_groups)).to contain_exactly(public_subgroup, private_subgroup)
       end
 
-      context 'being member' do
+      context 'being member of private subgroup' do
         it 'shows public and private subgroups the user is member of' do
+          group_member.destroy!
           private_subgroup.add_guest(user)
 
           get :subgroups, id: group.to_param
diff --git a/spec/controllers/projects/builds_controller_spec.rb b/spec/controllers/projects/builds_controller_spec.rb
index 3ce23c17cdc684d133c65410c632d0030b1d3255..025b845de30b66c8c60a07334bfd416f175ade2c 100644
--- a/spec/controllers/projects/builds_controller_spec.rb
+++ b/spec/controllers/projects/builds_controller_spec.rb
@@ -261,7 +261,11 @@ def post_retry
 
   describe 'POST play' do
     before do
-      project.add_master(user)
+      project.add_developer(user)
+
+      create(:protected_branch, :developers_can_merge,
+             name: 'master', project: project)
+
       sign_in(user)
 
       post_play
diff --git a/spec/controllers/projects/issues_controller_spec.rb b/spec/controllers/projects/issues_controller_spec.rb
index 1f79e72495ac7c0c8f5b50cc567fad28d034f5f7..60a327c43c0642a7f7d0f1f931a1430fccddb482 100644
--- a/spec/controllers/projects/issues_controller_spec.rb
+++ b/spec/controllers/projects/issues_controller_spec.rb
@@ -234,6 +234,7 @@ def update_spam_issue
             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/features/merge_requests/edit_mr_spec.rb b/spec/features/merge_requests/edit_mr_spec.rb
index b054d62a76600e73599b1c251bc6780b610ad5b0..8c145535039a612bfd379026458a31b8a5cf1f9d 100644
--- a/spec/features/merge_requests/edit_mr_spec.rb
+++ b/spec/features/merge_requests/edit_mr_spec.rb
@@ -27,6 +27,19 @@
       expect(page).to have_content 'Someone edited the merge request the same time you did'
     end
 
+    it 'allows to unselect "Remove source branch"', js: true do
+      merge_request.update(merge_params: { 'force_remove_source_branch' => '1' })
+      expect(merge_request.merge_params['force_remove_source_branch']).to be_truthy
+
+      visit edit_namespace_project_merge_request_path(project.namespace, project, merge_request)
+      uncheck 'Remove source branch when merge request is accepted'
+
+      click_button 'Save changes'
+
+      expect(page).to have_unchecked_field 'remove-source-branch-input'
+      expect(page).to have_content 'Remove source branch'
+    end
+
     it 'should preserve description textarea height', js: true do
       long_description = %q(
         Lorem ipsum dolor sit amet, consectetur adipiscing elit. Etiam ac ornare ligula, ut tempus arcu. Etiam ultricies accumsan dolor vitae faucibus. Donec at elit lacus. Mauris orci ante, aliquam quis lorem eget, convallis faucibus arcu. Aenean at pulvinar lacus. Ut viverra quam massa, molestie ornare tortor dignissim a. Suspendisse tristique pellentesque tellus, id lacinia metus elementum id. Nam tristique, arcu rhoncus faucibus viverra, lacus ipsum sagittis ligula, vitae convallis odio lacus a nibh. Ut tincidunt est purus, ac vestibulum augue maximus in. Suspendisse vel erat et mi ultricies semper. Pellentesque volutpat pellentesque consequat.
diff --git a/spec/features/merge_requests/merge_when_pipeline_succeeds_spec.rb b/spec/features/merge_requests/merge_when_pipeline_succeeds_spec.rb
index b33d7f90a31097f99fc380461b35283f68a4fef7..4e524393b26fdf6f10b19b687b870fe6aec66e48 100644
--- a/spec/features/merge_requests/merge_when_pipeline_succeeds_spec.rb
+++ b/spec/features/merge_requests/merge_when_pipeline_succeeds_spec.rb
@@ -7,7 +7,8 @@
   let(:merge_request) do
     create(:merge_request_with_diffs, source_project: project,
                                       author: user,
-                                      title: 'Bug NS-04')
+                                      title: 'Bug NS-04',
+                                      merge_params: { force_remove_source_branch: '1' })
   end
 
   let(:pipeline) do
@@ -38,7 +39,7 @@
           click_button "Merge when pipeline succeeds"
 
           expect(page).to have_content "Set by #{user.name} to be merged automatically when the pipeline succeeds."
-          expect(page).to have_content "The source branch will be removed."
+          expect(page).to have_content "The source branch will not be removed."
           expect(page).to have_selector ".js-cancel-auto-merge"
           visit_merge_request(merge_request) # Needed to refresh the page
           expect(page).to have_content /enabled an automatic merge when the pipeline for \h{8} succeeds/i
@@ -79,7 +80,8 @@
                    source_project: project,
                    title: 'Bug NS-04',
                    author: user,
-                   merge_user: user)
+                   merge_user: user,
+                   merge_params: { force_remove_source_branch: '1' })
         end
 
         before do
@@ -96,7 +98,7 @@
         click_link 'Merge when pipeline succeeds'
 
         expect(page).to have_content "Set by #{user.name} to be merged automatically when the pipeline succeeds."
-        expect(page).to have_content "The source branch will be removed."
+        expect(page).to have_content "The source branch will not be removed."
         expect(page).to have_link "Cancel automatic merge"
       end
     end
diff --git a/spec/features/merge_requests/squash_spec.rb b/spec/features/merge_requests/squash_spec.rb
index 1803b027b82f6d1b40df3bdae9e15113539493e0..d32a40ce87eea8f8870b6ff642cf7590d5bd76d2 100644
--- a/spec/features/merge_requests/squash_spec.rb
+++ b/spec/features/merge_requests/squash_spec.rb
@@ -45,6 +45,9 @@ def accept_mr
   end
 
   before do
+    # Prevent source branch from being removed so we can use be_merged_to_root_ref
+    # method to check if squash was performed or not
+    allow_any_instance_of(MergeRequest).to receive(:force_remove_source_branch?).and_return(false)
     project.team << [user, :master]
 
     login_as user
diff --git a/spec/features/merge_requests/widget_spec.rb b/spec/features/merge_requests/widget_spec.rb
index 8dd69d193cb03ca0e6e6158f4fcac71cceeab9e3..862eaf724a0595059e18f14c4a11f8ca99dd30f4 100644
--- a/spec/features/merge_requests/widget_spec.rb
+++ b/spec/features/merge_requests/widget_spec.rb
@@ -220,4 +220,25 @@
       end
     end
   end
+
+  context 'user can merge into source project but cannot push to fork', js: true do
+    let(:fork_project) { create(:project, :public) }
+    let(:user2) { create(:user) }
+
+    before do
+      project.team << [user2, :master]
+      logout
+      login_as user2
+      merge_request.update(target_project: fork_project)
+      visit namespace_project_merge_request_path(project.namespace, project, merge_request)
+    end
+
+    it 'user can merge into the source project' do
+      expect(page).to have_button('Merge', disabled: false)
+    end
+
+    it 'user cannot remove source branch' do
+      expect(page).to have_field('remove-source-branch-input', disabled: true)
+    end
+  end
 end
diff --git a/spec/features/projects/environments/environment_spec.rb b/spec/features/projects/environments/environment_spec.rb
index 86ce50c976f0a7591e33651216ae6362d37219e6..18b608c863efe50bdfe0516692986d6d86af835e 100644
--- a/spec/features/projects/environments/environment_spec.rb
+++ b/spec/features/projects/environments/environment_spec.rb
@@ -12,6 +12,7 @@
 
   feature 'environment details page' do
     given!(:environment) { create(:environment, project: project) }
+    given!(:permissions) { }
     given!(:deployment) { }
     given!(:action) { }
 
@@ -62,20 +63,31 @@
                                        name: 'deploy to production')
           end
 
-          given(:role) { :master }
+          context 'when user has ability to trigger deployment' do
+            given(:permissions) do
+              create(:protected_branch, :developers_can_merge,
+                     name: action.ref, project: project)
+            end
 
-          scenario 'does show a play button' do
-            expect(page).to have_link(action.name.humanize)
-          end
+            it 'does show a play button' do
+              expect(page).to have_link(action.name.humanize)
+            end
+
+            it 'does allow to play manual action' do
+              expect(action).to be_manual
 
-          scenario 'does allow to play manual action' do
-            expect(action).to be_manual
+              expect { click_link(action.name.humanize) }
+                .not_to change { Ci::Pipeline.count }
 
-            expect { click_link(action.name.humanize) }
-              .not_to change { Ci::Pipeline.count }
+              expect(page).to have_content(action.name)
+              expect(action.reload).to be_pending
+            end
+          end
 
-            expect(page).to have_content(action.name)
-            expect(action.reload).to be_pending
+          context 'when user has no ability to trigger a deployment' do
+            it 'does not show a play button' do
+              expect(page).not_to have_link(action.name.humanize)
+            end
           end
 
           context 'with external_url' do
@@ -134,12 +146,23 @@
                                     on_stop: 'close_app')
               end
 
-              given(:role) { :master }
+              context 'when user has ability to stop environment' do
+                given(:permissions) do
+                  create(:protected_branch, :developers_can_merge,
+                         name: action.ref, project: project)
+                end
 
-              scenario 'does allow to stop environment' do
-                click_link('Stop')
+                it 'allows to stop environment' do
+                  click_link('Stop')
 
-                expect(page).to have_content('close_app')
+                  expect(page).to have_content('close_app')
+                end
+              end
+
+              context 'when user has no ability to stop environment' do
+                it 'does not allow to stop environment' do
+                  expect(page).to have_no_link('Stop')
+                end
               end
 
               context 'for reporter' do
@@ -150,12 +173,6 @@
                 end
               end
             end
-
-            context 'without stop action' do
-              scenario 'does allow to stop environment' do
-                click_link('Stop')
-              end
-            end
           end
 
           context 'when environment is stopped' do
diff --git a/spec/features/projects/pipeline_schedules_spec.rb b/spec/features/projects/pipeline_schedules_spec.rb
index 1211b17b3d8de56548baa67eea65354c1def14b1..39b4dd7235ba93657e4b5234fca8ee09ff1a8bb6 100644
--- a/spec/features/projects/pipeline_schedules_spec.rb
+++ b/spec/features/projects/pipeline_schedules_spec.rb
@@ -5,7 +5,7 @@
   include WaitForAjax
 
   let!(:project) { create(:project) }
-  let!(:pipeline_schedule) { create(:ci_pipeline_schedule, project: project) }
+  let!(:pipeline_schedule) { create(:ci_pipeline_schedule, :nightly, project: project ) }
   let!(:pipeline) { create(:ci_pipeline, pipeline_schedule: pipeline_schedule) }
   let(:scope) { nil }
   let!(:user) { create(:user) }
@@ -32,6 +32,7 @@
       it 'displays the required information description' do
         page.within('.pipeline-schedule-table-row') do
           expect(page).to have_content('pipeline schedule')
+          expect(page).to have_content(pipeline_schedule.real_next_run.strftime('%b %d, %Y'))
           expect(page).to have_link('master')
           expect(page).to have_link("##{pipeline.id}")
         end
@@ -65,6 +66,17 @@
         expect(page).not_to have_content('pipeline schedule')
       end
     end
+
+    context 'when ref is nil' do
+      before do
+        pipeline_schedule.update_attribute(:ref, nil)
+        visit_pipelines_schedules
+      end
+
+      it 'shows a list of the pipeline schedules with empty ref column' do
+        expect(first('.branch-name-cell').text).to eq('')
+      end
+    end
   end
 
   describe 'POST /projects/pipeline_schedules/new', js: true do
@@ -108,6 +120,19 @@
 
       expect(page).to have_content('my brand new description')
     end
+
+    context 'when ref is nil' do
+      before do
+        pipeline_schedule.update_attribute(:ref, nil)
+        edit_pipeline_schedule
+      end
+
+      it 'shows the pipeline schedule with default ref' do
+        page.within('.git-revision-dropdown-toggle') do
+          expect(first('.dropdown-toggle-text').text).to eq('master')
+        end
+      end
+    end
   end
 
   def visit_new_pipeline_schedule
diff --git a/spec/features/projects/sub_group_issuables_spec.rb b/spec/features/projects/sub_group_issuables_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..cf21b208f65e951482607fe3fbbbe28cda7c85b4
--- /dev/null
+++ b/spec/features/projects/sub_group_issuables_spec.rb
@@ -0,0 +1,32 @@
+require 'spec_helper'
+
+describe 'Subgroup Issuables', :feature, :js do
+  let!(:group)    { create(:group, name: 'group') }
+  let!(:subgroup) { create(:group, parent: group, name: 'subgroup') }
+  let!(:project)  { create(:empty_project, namespace: subgroup, name: 'project') }
+  let(:user)      { create(:user) }
+
+  before do
+    project.add_master(user)
+    login_as user
+  end
+
+  it 'shows the full subgroup title when issues index page is empty' do
+    visit namespace_project_issues_path(project.namespace.to_param, project.to_param)
+
+    expect_to_have_full_subgroup_title
+  end
+
+  it 'shows the full subgroup title when merge requests index page is empty' do
+    visit namespace_project_merge_requests_path(project.namespace.to_param, project.to_param)
+
+    expect_to_have_full_subgroup_title
+  end
+
+  def expect_to_have_full_subgroup_title
+    title = find('.title-container')
+
+    expect(title).not_to have_selector '.initializing'
+    expect(title).to have_content 'group / subgroup / project'
+  end
+end
diff --git a/spec/finders/groups_finder_spec.rb b/spec/finders/groups_finder_spec.rb
index 5b3591550c130b19f42658266f606a46931c1ace..e082631ce685f27b411761690d6d2d6e68003bb6 100644
--- a/spec/finders/groups_finder_spec.rb
+++ b/spec/finders/groups_finder_spec.rb
@@ -51,15 +51,48 @@
       end
 
       context 'with a user' do
+        subject { described_class.new(user, parent: parent_group).execute }
+
         it 'returns public and internal subgroups' do
-          expect(described_class.new(user, parent: parent_group).execute).to contain_exactly(public_subgroup, internal_subgroup)
+          is_expected.to contain_exactly(public_subgroup, internal_subgroup)
         end
 
         context 'being member' do
           it 'returns public subgroups, internal subgroups, and private subgroups user is member of' do
             private_subgroup.add_guest(user)
 
-            expect(described_class.new(user, parent: parent_group).execute).to contain_exactly(public_subgroup, internal_subgroup, private_subgroup)
+            is_expected.to contain_exactly(public_subgroup, internal_subgroup, private_subgroup)
+          end
+        end
+
+        context 'parent group private' do
+          before do
+            parent_group.update_attribute(:visibility_level, Gitlab::VisibilityLevel::PRIVATE)
+          end
+
+          context 'being member of parent group' do
+            it 'returns all subgroups' do
+              parent_group.add_guest(user)
+
+              is_expected.to contain_exactly(public_subgroup, internal_subgroup, private_subgroup)
+            end
+          end
+
+          context 'authorized to private project' do
+            it 'returns the subgroup of the project' do
+              subproject = create(:empty_project, :private, namespace: private_subgroup)
+              subproject.add_guest(user)
+
+              is_expected.to include(private_subgroup)
+            end
+
+            it 'returns all the parent groups if project is several levels deep' do
+              private_subsubgroup = create(:group, :private, parent: private_subgroup)
+              subsubproject = create(:empty_project, :private, namespace: private_subsubgroup)
+              subsubproject.add_guest(user)
+
+              expect(described_class.new(user).execute).to include(private_subsubgroup, private_subgroup, parent_group)
+            end
           end
         end
       end
diff --git a/spec/fixtures/api/schemas/entities/merge_request.json b/spec/fixtures/api/schemas/entities/merge_request.json
index 8e1820c933eb1fb6cbde1296e0e6ebc2979556e3..1a0cceb1cafa12aa04233267069de7cca4c26749 100644
--- a/spec/fixtures/api/schemas/entities/merge_request.json
+++ b/spec/fixtures/api/schemas/entities/merge_request.json
@@ -92,6 +92,8 @@
     "diverged_commits_count": { "type": "integer" },
     "commit_change_content_path": { "type": "string" },
     "remove_wip_path": { "type": "string" },
+    "commits_count": { "type": "integer" },
+    "remove_source_branch": { "type": ["boolean", "null"] },
     // EE-specific
     "rebase_commit_sha": { "type": ["string", "null"] },
     "approvals_before_merge": { "type": ["integer", "null"] },
diff --git a/spec/helpers/submodule_helper_spec.rb b/spec/helpers/submodule_helper_spec.rb
index 9da3379265957da223353da196f6b77547881c08..fa9945f3d73f590d4b8829f7e0d7749fb7555e01 100644
--- a/spec/helpers/submodule_helper_spec.rb
+++ b/spec/helpers/submodule_helper_spec.rb
@@ -102,6 +102,11 @@
         expect(submodule_links(submodule_item)).to eq(['https://gitlab.com/gitlab-org/gitlab-ce', 'https://gitlab.com/gitlab-org/gitlab-ce/tree/hash'])
       end
 
+      it 'handles urls with trailing whitespace' do
+        stub_url('http://gitlab.com/gitlab-org/gitlab-ce.git  ')
+        expect(submodule_links(submodule_item)).to eq(['https://gitlab.com/gitlab-org/gitlab-ce', 'https://gitlab.com/gitlab-org/gitlab-ce/tree/hash'])
+      end
+
       it 'returns original with non-standard url' do
         stub_url('http://gitlab.com/another/gitlab-org/gitlab-ce.git')
         expect(submodule_links(submodule_item)).to eq([repo.submodule_url_for, nil])
diff --git a/spec/javascripts/fixtures/merge_requests.rb b/spec/javascripts/fixtures/merge_requests.rb
index 47d904b865bf43550bc83a87df524fa93f3b3e34..a746a77654887a978b25c4b26f621a97c1656508 100644
--- a/spec/javascripts/fixtures/merge_requests.rb
+++ b/spec/javascripts/fixtures/merge_requests.rb
@@ -16,6 +16,16 @@
       sha: merge_request.diff_head_sha
     )
   end
+  let(:path) { "files/ruby/popen.rb" }
+  let(:position) do
+    Gitlab::Diff::Position.new(
+      old_path: path,
+      new_path: path,
+      old_line: nil,
+      new_line: 14,
+      diff_refs: merge_request.diff_refs
+    )
+  end
 
   render_views
 
@@ -39,6 +49,12 @@
     render_merge_request(example.description, merged_merge_request)
   end
 
+  it 'merge_requests/diff_comment.html.raw' do |example|
+    create(:diff_note_on_merge_request, project: project, author: admin, position: position, noteable: merge_request)
+    create(:note_on_merge_request, author: admin, project: project, noteable: merge_request)
+    render_merge_request(example.description, merge_request)
+  end
+
   private
 
   def render_merge_request(fixture_file_name, merge_request)
diff --git a/spec/javascripts/lib/utils/common_utils_spec.js b/spec/javascripts/lib/utils/common_utils_spec.js
index 5eb147ed888063a83224e184fd6bae18085b3de8..42a9067ade5ea8f5889cb1d459c8dbaa07134c84 100644
--- a/spec/javascripts/lib/utils/common_utils_spec.js
+++ b/spec/javascripts/lib/utils/common_utils_spec.js
@@ -41,6 +41,16 @@ require('~/lib/utils/common_utils');
         const paramsArray = gl.utils.getUrlParamsArray();
         expect(paramsArray[0][0] !== '?').toBe(true);
       });
+
+      it('should decode params', () => {
+        history.pushState('', '', '?label_name%5B%5D=test');
+
+        expect(
+          gl.utils.getUrlParamsArray()[0],
+        ).toBe('label_name[]=test');
+
+        history.pushState('', '', '?');
+      });
     });
 
     describe('gl.utils.handleLocationHash', () => {
diff --git a/spec/javascripts/merge_request_tabs_spec.js b/spec/javascripts/merge_request_tabs_spec.js
index e437333d5221b23f3779fbc49b773238a7371b1f..d119fc0c11e3fecb55af3ebbbf19a284c167a7fd 100644
--- a/spec/javascripts/merge_request_tabs_spec.js
+++ b/spec/javascripts/merge_request_tabs_spec.js
@@ -1,4 +1,5 @@
 /* eslint-disable no-var, comma-dangle, object-shorthand */
+/* global Notes */
 
 require('~/merge_request_tabs');
 require('~/commit/pipelines/pipelines_bundle.js');
@@ -7,6 +8,7 @@ require('~/lib/utils/common_utils');
 require('~/diff');
 require('~/single_file_diff');
 require('~/files_comment_button');
+require('~/notes');
 require('vendor/jquery.scrollTo');
 
 (function () {
@@ -29,7 +31,7 @@ require('vendor/jquery.scrollTo');
       };
       $.extend(stubLocation, defaults, stubs || {});
     };
-    preloadFixtures('merge_requests/merge_request_with_task_list.html.raw');
+    preloadFixtures('merge_requests/merge_request_with_task_list.html.raw', 'merge_requests/diff_comment.html.raw');
 
     beforeEach(function () {
       this.class = new gl.MergeRequestTabs({ stubLocation: stubLocation });
@@ -286,8 +288,49 @@ require('vendor/jquery.scrollTo');
         spyOn($, 'ajax').and.callFake(function (options) {
           expect(options.url).toEqual('/foo/bar/merge_requests/1/diffs.json');
         });
+
         this.class.loadDiff('/foo/bar/merge_requests/1/diffs');
       });
+
+      describe('with note fragment hash', () => {
+        beforeEach(() => {
+          loadFixtures('merge_requests/diff_comment.html.raw');
+          spyOn(window.gl.utils, 'getPagePath').and.returnValue('merge_requests');
+          window.notes = new Notes('', []);
+          spyOn(window.notes, 'toggleDiffNote').and.callThrough();
+        });
+
+        afterEach(() => {
+          delete window.notes;
+        });
+
+        it('should expand and scroll to linked fragment hash #note_xxx', function () {
+          const noteId = 'note_1';
+          spyOn(window.gl.utils, 'getLocationHash').and.returnValue(noteId);
+          spyOn($, 'ajax').and.callFake(function (options) {
+            options.success({ html: `<div id="${noteId}">foo</div>` });
+          });
+
+          this.class.loadDiff('/foo/bar/merge_requests/1/diffs');
+
+          expect(window.notes.toggleDiffNote).toHaveBeenCalledWith({
+            target: jasmine.any(Object),
+            lineType: 'old',
+            forceShow: true,
+          });
+        });
+
+        it('should gracefully ignore non-existant fragment hash', function () {
+          spyOn(window.gl.utils, 'getLocationHash').and.returnValue('note_something-that-does-not-exist');
+          spyOn($, 'ajax').and.callFake(function (options) {
+            options.success({ html: '' });
+          });
+
+          this.class.loadDiff('/foo/bar/merge_requests/1/diffs');
+
+          expect(window.notes.toggleDiffNote).not.toHaveBeenCalled();
+        });
+      });
     });
   });
 }).call(window);
diff --git a/spec/javascripts/vue_mr_widget/components/states/mr_widget_ready_to_merge_spec.js b/spec/javascripts/vue_mr_widget/components/states/mr_widget_ready_to_merge_spec.js
index d043ad38b8bd771ce44cd3337b7d206b94242eef..732b516badd301a9c038fe1c5019dbf52f2eb175 100644
--- a/spec/javascripts/vue_mr_widget/components/states/mr_widget_ready_to_merge_spec.js
+++ b/spec/javascripts/vue_mr_widget/components/states/mr_widget_ready_to_merge_spec.js
@@ -5,7 +5,7 @@ import * as simplePoll from '~/lib/utils/simple_poll';
 
 const commitMessage = 'This is the commit message';
 const commitMessageWithDescription = 'This is the commit message description';
-const createComponent = () => {
+const createComponent = (customConfig = {}) => {
   const Component = Vue.extend(readyToMergeComponent);
   const mr = {
     isPipelineActive: false,
@@ -17,8 +17,12 @@ const createComponent = () => {
     sha: '12345678',
     commitMessage,
     commitMessageWithDescription,
+    shouldRemoveSourceBranch: true,
+    canRemoveSourceBranch: false,
   };
 
+  Object.assign(mr, customConfig.mr);
+
   const service = {
     merge() {},
     poll() {},
@@ -51,7 +55,6 @@ describe('MRWidgetReadyToMerge', () => {
 
   describe('data', () => {
     it('should have default data', () => {
-      expect(vm.removeSourceBranch).toBeTruthy(true);
       expect(vm.mergeWhenBuildSucceeds).toBeFalsy();
       expect(vm.useCommitMessageWithDescription).toBeFalsy();
       expect(vm.setToMergeWhenPipelineSucceeds).toBeFalsy();
@@ -166,6 +169,36 @@ describe('MRWidgetReadyToMerge', () => {
         expect(vm.isMergeButtonDisabled).toBeTruthy();
       });
     });
+
+    describe('Remove source branch checkbox', () => {
+      describe('when user can merge but cannot delete branch', () => {
+        it('isRemoveSourceBranchButtonDisabled should be true', () => {
+          expect(vm.isRemoveSourceBranchButtonDisabled).toBe(true);
+        });
+
+        it('should be disabled in the rendered output', () => {
+          const checkboxElement = vm.$el.querySelector('#remove-source-branch-input');
+          expect(checkboxElement.getAttribute('disabled')).toBe('disabled');
+        });
+      });
+
+      describe('when user can merge and can delete branch', () => {
+        beforeEach(() => {
+          this.customVm = createComponent({
+            mr: { canRemoveSourceBranch: true },
+          });
+        });
+
+        it('isRemoveSourceBranchButtonDisabled should be false', () => {
+          expect(this.customVm.isRemoveSourceBranchButtonDisabled).toBe(false);
+        });
+
+        it('should be enabled in rendered output', () => {
+          const checkboxElement = this.customVm.$el.querySelector('#remove-source-branch-input');
+          expect(checkboxElement.getAttribute('disabled')).toBeNull();
+        });
+      });
+    });
   });
 
   describe('methods', () => {
diff --git a/spec/javascripts/vue_mr_widget/mr_widget_options_spec.js b/spec/javascripts/vue_mr_widget/mr_widget_options_spec.js
index bdc18243a15413ded59472298ff15cc7fa07d389..3a0c50b750ffeb91a54746005e100cbe40992427 100644
--- a/spec/javascripts/vue_mr_widget/mr_widget_options_spec.js
+++ b/spec/javascripts/vue_mr_widget/mr_widget_options_spec.js
@@ -2,6 +2,7 @@ import Vue from 'vue';
 import MRWidgetService from '~/vue_merge_request_widget/services/mr_widget_service';
 import mrWidgetOptions from '~/vue_merge_request_widget/mr_widget_options';
 import eventHub from '~/vue_merge_request_widget/event_hub';
+import notify from '~/lib/utils/notify';
 import mockData from './mock_data';
 
 const createComponent = () => {
@@ -107,6 +108,8 @@ describe('mrWidgetOptions', () => {
       it('should tell service to check status', (done) => {
         spyOn(vm.service, 'checkStatus').and.returnValue(returnPromise(mockData));
         spyOn(vm.mr, 'setData');
+        spyOn(vm, 'handleNotification');
+
         let isCbExecuted = false;
         const cb = () => {
           isCbExecuted = true;
@@ -117,6 +120,7 @@ describe('mrWidgetOptions', () => {
         setTimeout(() => {
           expect(vm.service.checkStatus).toHaveBeenCalled();
           expect(vm.mr.setData).toHaveBeenCalled();
+          expect(vm.handleNotification).toHaveBeenCalledWith(mockData);
           expect(isCbExecuted).toBeTruthy();
           done();
         }, 333);
@@ -254,6 +258,39 @@ describe('mrWidgetOptions', () => {
       });
     });
 
+    describe('handleNotification', () => {
+      const data = {
+        ci_status: 'running',
+        title: 'title',
+        pipeline: { details: { status: { label: 'running-label' } } },
+      };
+
+      beforeEach(() => {
+        spyOn(notify, 'notifyMe');
+
+        vm.mr.ciStatus = 'failed';
+        vm.mr.gitlabLogo = 'logo.png';
+      });
+
+      it('should call notifyMe', () => {
+        vm.handleNotification(data);
+
+        expect(notify.notifyMe).toHaveBeenCalledWith(
+          'Pipeline running-label',
+          'Pipeline running-label for "title"',
+          'logo.png',
+        );
+      });
+
+      it('should not call notifyMe if the status has not changed', () => {
+        vm.mr.ciStatus = data.ci_status;
+
+        vm.handleNotification(data);
+
+        expect(notify.notifyMe).not.toHaveBeenCalled();
+      });
+    });
+
     describe('resumePolling', () => {
       it('should call stopTimer on pollingInterval', () => {
         spyOn(vm.pollingInterval, 'resume');
diff --git a/spec/lib/gitlab/backup/repository_spec.rb b/spec/lib/gitlab/backup/repository_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..51c1e9d657b4ed91e20b2bca0caa5278d7915354
--- /dev/null
+++ b/spec/lib/gitlab/backup/repository_spec.rb
@@ -0,0 +1,63 @@
+require 'spec_helper'
+
+describe Backup::Repository, lib: true do
+  let(:progress) { StringIO.new }
+  let!(:project) { create(:empty_project) }
+
+  before do
+    allow(progress).to receive(:puts)
+    allow(progress).to receive(:print)
+
+    allow_any_instance_of(String).to receive(:color) do |string, _color|
+      string
+    end
+
+    allow_any_instance_of(described_class).to receive(:progress).and_return(progress)
+  end
+
+  describe '#dump' do
+    describe 'repo failure' do
+      before do
+        allow_any_instance_of(Repository).to receive(:empty_repo?).and_raise(Rugged::OdbError)
+        allow(Gitlab::Popen).to receive(:popen).and_return(['normal output', 0])
+      end
+
+      it 'does not raise error' do
+        expect { described_class.new.dump }.not_to raise_error
+      end
+
+      it 'shows the appropriate error' do
+        described_class.new.dump
+
+        expect(progress).to have_received(:puts).with("Ignoring repository error and continuing backing up project: #{project.full_path} - Rugged::OdbError")
+      end
+    end
+
+    describe 'command failure' do
+      before do
+        allow_any_instance_of(Repository).to receive(:empty_repo?).and_return(false)
+        allow(Gitlab::Popen).to receive(:popen).and_return(['error', 1])
+      end
+
+      it 'shows the appropriate error' do
+        described_class.new.dump
+
+        expect(progress).to have_received(:puts).with("Ignoring error on #{project.full_path} - error")
+      end
+    end
+  end
+
+  describe '#restore' do
+    describe 'command failure' do
+      before do
+        allow(Gitlab::Popen).to receive(:popen).and_return(['error', 1])
+      end
+
+      it 'shows the appropriate error' do
+        described_class.new.restore
+
+        expect(progress).to have_received(:puts).with("Ignoring error on #{project.full_path} - error")
+      end
+    end
+  end
+end
diff --git a/spec/lib/gitlab/chat_commands/command_spec.rb b/spec/lib/gitlab/chat_commands/command_spec.rb
index eb4f06b371c4346b5b857b9d9a74e57168496c5f..13e6953147bca8246bd4f9d8241c8946ca7b25ca 100644
--- a/spec/lib/gitlab/chat_commands/command_spec.rb
+++ b/spec/lib/gitlab/chat_commands/command_spec.rb
@@ -58,9 +58,12 @@
         end
       end
 
-      context 'and user does have deployment permission' do
+      context 'and user has deployment permission' do
         before do
-          build.project.add_master(user)
+          build.project.add_developer(user)
+
+          create(:protected_branch, :developers_can_merge,
+                 name: build.ref, project: project)
         end
 
         it 'returns action' do
diff --git a/spec/lib/gitlab/chat_commands/deploy_spec.rb b/spec/lib/gitlab/chat_commands/deploy_spec.rb
index b33389d959e30dd52e93cde9aad3cf2af515e15c..46dbdeae37cea2475caca685462c01da3eef5814 100644
--- a/spec/lib/gitlab/chat_commands/deploy_spec.rb
+++ b/spec/lib/gitlab/chat_commands/deploy_spec.rb
@@ -7,7 +7,12 @@
     let(:regex_match) { described_class.match('deploy staging to production') }
 
     before do
-      project.add_master(user)
+      # Make it possible to trigger protected manual actions for developers.
+      #
+      project.add_developer(user)
+
+      create(:protected_branch, :developers_can_merge,
+             name: 'master', project: project)
     end
 
     subject do
diff --git a/spec/lib/gitlab/ci/status/build/factory_spec.rb b/spec/lib/gitlab/ci/status/build/factory_spec.rb
index 185bb9098dac01ccd01022e7c049f58dbdc72210..3f30b2c38f21a4b508002bbc081bfc1105c6aa27 100644
--- a/spec/lib/gitlab/ci/status/build/factory_spec.rb
+++ b/spec/lib/gitlab/ci/status/build/factory_spec.rb
@@ -224,7 +224,10 @@
 
       context 'when user has ability to play action' do
         before do
-          build.project.add_master(user)
+          project.add_developer(user)
+
+          create(:protected_branch, :developers_can_merge,
+                 name: build.ref, project: project)
         end
 
         it 'fabricates status that has action' do
diff --git a/spec/lib/gitlab/ci/status/build/play_spec.rb b/spec/lib/gitlab/ci/status/build/play_spec.rb
index f5d0f977768fc925e43d362c04a84eedefb9368a..0e15a5f3c6b428821e0d242c7e0dbc48f34945ee 100644
--- a/spec/lib/gitlab/ci/status/build/play_spec.rb
+++ b/spec/lib/gitlab/ci/status/build/play_spec.rb
@@ -2,6 +2,7 @@
 
 describe Gitlab::Ci::Status::Build::Play do
   let(:user) { create(:user) }
+  let(:project) { build.project }
   let(:build) { create(:ci_build, :manual) }
   let(:status) { Gitlab::Ci::Status::Core.new(build, user) }
 
@@ -15,8 +16,13 @@
 
   describe '#has_action?' do
     context 'when user is allowed to update build' do
-      context 'when user can push to branch' do
-        before { build.project.add_master(user) }
+      context 'when user is allowed to trigger protected action' do
+        before do
+          project.add_developer(user)
+
+          create(:protected_branch, :developers_can_merge,
+                 name: build.ref, project: project)
+        end
 
         it { is_expected.to have_action }
       end
diff --git a/spec/lib/gitlab/ci/trace/stream_spec.rb b/spec/lib/gitlab/ci/trace/stream_spec.rb
index 40ac5a3ed375f43ad95d3c7bd0853c507776750d..bbb3f9912a3e92401c0e86ba2af992fa7b194c1b 100644
--- a/spec/lib/gitlab/ci/trace/stream_spec.rb
+++ b/spec/lib/gitlab/ci/trace/stream_spec.rb
@@ -240,9 +240,50 @@
     end
 
     context 'multiple results in content & regex' do
-      let(:data) { ' (98.39%) covered. (98.29%) covered' }
+      let(:data) do
+        <<~HEREDOC
+          (98.39%) covered
+          (98.29%) covered
+        HEREDOC
+      end
+
+      let(:regex) { '\(\d+.\d+\%\) covered' }
+
+      it 'returns the last matched coverage' do
+        is_expected.to eq("98.29")
+      end
+    end
+
+    context 'when BUFFER_SIZE is smaller than stream.size' do
+      let(:data) { 'Coverage 1033 / 1051 LOC (98.29%) covered\n' }
+      let(:regex) { '\(\d+.\d+\%\) covered' }
+
+      before do
+        stub_const('Gitlab::Ci::Trace::Stream::BUFFER_SIZE', 5)
+      end
+
+      it { is_expected.to eq("98.29") }
+    end
+
+    context 'when regex is multi-byte char' do
+      let(:data) { '95.0 ゴッドファット\n' }
+      let(:regex) { '\d+\.\d+ ゴッドファット' }
+
+      before do
+        stub_const('Gitlab::Ci::Trace::Stream::BUFFER_SIZE', 5)
+      end
+
+      it { is_expected.to eq('95.0') }
+    end
+
+    context 'when BUFFER_SIZE is equal to stream.size' do
+      let(:data) { 'Coverage 1033 / 1051 LOC (98.29%) covered\n' }
       let(:regex) { '\(\d+.\d+\%\) covered' }
 
+      before do
+        stub_const('Gitlab::Ci::Trace::Stream::BUFFER_SIZE', data.length)
+      end
+
       it { is_expected.to eq("98.29") }
     end
 
diff --git a/spec/lib/gitlab/etag_caching/middleware_spec.rb b/spec/lib/gitlab/etag_caching/middleware_spec.rb
index 24df04e985a18917ec2667668bd727cca55c6e0b..3c6ef7c7ccb29527f80f8c92a1cdc317e32cb115 100644
--- a/spec/lib/gitlab/etag_caching/middleware_spec.rb
+++ b/spec/lib/gitlab/etag_caching/middleware_spec.rb
@@ -164,6 +164,25 @@
     end
   end
 
+  context 'when GitLab instance is using a relative URL' do
+    before do
+      mock_app_response
+    end
+
+    it 'uses full path as cache key' do
+      env = {
+        'PATH_INFO' => enabled_path,
+        'SCRIPT_NAME' => '/relative-gitlab'
+      }
+
+      expect_any_instance_of(Gitlab::EtagCaching::Store)
+        .to receive(:get).with("/relative-gitlab#{enabled_path}")
+        .and_return(nil)
+
+      middleware.call(env)
+    end
+  end
+
   def mock_app_response
     allow(app).to receive(:call).and_return([app_status_code, {}, ['body']])
   end
diff --git a/spec/lib/gitlab/etag_caching/router_spec.rb b/spec/lib/gitlab/etag_caching/router_spec.rb
index 410df116a3a8dc442f6ee10936d754204078f125..5eafce9cb8f3d6be0dc46ee17b8982a136656d6f 100644
--- a/spec/lib/gitlab/etag_caching/router_spec.rb
+++ b/spec/lib/gitlab/etag_caching/router_spec.rb
@@ -2,93 +2,93 @@
 
 describe Gitlab::EtagCaching::Router do
   it 'matches issue notes endpoint' do
-    env = build_env(
+    request = build_request(
       '/my-group/and-subgroup/here-comes-the-project/noteable/issue/1/notes'
     )
 
-    result = described_class.match(env)
+    result = described_class.match(request)
 
     expect(result).to be_present
     expect(result.name).to eq 'issue_notes'
   end
 
   it 'matches issue title endpoint' do
-    env = build_env(
+    request = build_request(
       '/my-group/my-project/issues/123/rendered_title'
     )
 
-    result = described_class.match(env)
+    result = described_class.match(request)
 
     expect(result).to be_present
     expect(result.name).to eq 'issue_title'
   end
 
   it 'matches project pipelines endpoint' do
-    env = build_env(
+    request = build_request(
       '/my-group/my-project/pipelines.json'
     )
 
-    result = described_class.match(env)
+    result = described_class.match(request)
 
     expect(result).to be_present
     expect(result.name).to eq 'project_pipelines'
   end
 
   it 'matches commit pipelines endpoint' do
-    env = build_env(
+    request = build_request(
       '/my-group/my-project/commit/aa8260d253a53f73f6c26c734c72fdd600f6e6d4/pipelines.json'
     )
 
-    result = described_class.match(env)
+    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
-    env = build_env(
+    request = build_request(
       '/my-group/my-project/merge_requests/new.json'
     )
 
-    result = described_class.match(env)
+    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
-    env = build_env(
+    request = build_request(
       '/my-group/my-project/merge_requests/234/pipelines.json'
     )
 
-    result = described_class.match(env)
+    result = described_class.match(request)
 
     expect(result).to be_present
     expect(result.name).to eq 'merge_request_pipelines'
   end
 
   it 'does not match blob with confusing name' do
-    env = build_env(
+    request = build_request(
       '/my-group/my-project/blob/master/pipelines.json'
     )
 
-    result = described_class.match(env)
+    result = described_class.match(request)
 
     expect(result).to be_blank
   end
 
   it 'matches pipeline#show endpoint' do
-    env = build_env(
+    request = build_request(
       '/my-group/my-project/pipelines/2.json'
     )
 
-    result = described_class.match(env)
+    result = described_class.match(request)
 
     expect(result).to be_present
     expect(result.name).to eq 'project_pipeline'
   end
 
-  def build_env(path)
-    { 'PATH_INFO' => path }
+  def build_request(path)
+    double(path_info: path)
   end
 end
diff --git a/spec/models/ci/pipeline_schedule_spec.rb b/spec/models/ci/pipeline_schedule_spec.rb
index 822b98c5f6c9a40575d5d7f96b6c92e1df0f57af..b00e7a735712fb29396e7789e15e19d8bdd22651 100644
--- a/spec/models/ci/pipeline_schedule_spec.rb
+++ b/spec/models/ci/pipeline_schedule_spec.rb
@@ -25,6 +25,14 @@
 
       expect(pipeline_schedule).not_to be_valid
     end
+
+    context 'when active is false' do
+      it 'does not allow nullified ref' do
+        pipeline_schedule = build(:ci_pipeline_schedule, :inactive, ref: nil)
+
+        expect(pipeline_schedule).not_to be_valid
+      end
+    end
   end
 
   describe '#set_next_run_at' do
diff --git a/spec/models/environment_spec.rb b/spec/models/environment_spec.rb
index bf75ab0f163d4819d304ad3a00f19720ba1c8132..7a967d77adaf5d7e8ba249e621638016c4ef1324 100644
--- a/spec/models/environment_spec.rb
+++ b/spec/models/environment_spec.rb
@@ -227,7 +227,10 @@
 
       context 'when user is allowed to stop environment' do
         before do
-          project.add_master(user)
+          project.add_developer(user)
+
+          create(:protected_branch, :developers_can_merge,
+                 name: 'master', project: project)
         end
 
         context 'when action did not yet finish' do
diff --git a/spec/models/label_spec.rb b/spec/models/label_spec.rb
index 80ca19acddae6a8255261680883e067dca6fb26e..84867e3d96b1fcc7a6f928d8efb8cdfa5c7e7548 100644
--- a/spec/models/label_spec.rb
+++ b/spec/models/label_spec.rb
@@ -49,6 +49,23 @@
 
       expect(label.color).to eq('#abcdef')
     end
+
+    it 'uses default color if color is missing' do
+      label = described_class.new(color: nil)
+
+      expect(label.color).to be(Label::DEFAULT_COLOR)
+    end
+  end
+
+  describe '#text_color' do
+    it 'uses default color if color is missing' do
+      expect(LabelsHelper).to receive(:text_color_for_bg).with(Label::DEFAULT_COLOR).
+        and_return(spy)
+
+      label = described_class.new(color: nil)
+
+      label.text_color
+    end
   end
 
   describe '#title' do
diff --git a/spec/models/milestone_spec.rb b/spec/models/milestone_spec.rb
index 432beb7e693ba84c6978e29a2d7c6d0053cc6c9a..22f96f5dae2e8a655a905bb8fc4b0431895bd5a3 100644
--- a/spec/models/milestone_spec.rb
+++ b/spec/models/milestone_spec.rb
@@ -251,4 +251,17 @@
       expect(milestone.to_reference(another_project)).to eq "sample-project%1"
     end
   end
+
+  describe '#participants' do
+    let(:project) { build(:empty_project, name: 'sample-project') }
+    let(:milestone) { build(:milestone, iid: 1, project: project) }
+
+    it 'returns participants without duplicates' do
+      user = create :user
+      create :issue, project: project, milestone: milestone, assignees: [user]
+      create :issue, project: project, milestone: milestone, assignees: [user]
+
+      expect(milestone.participants).to eq [user]
+    end
+  end
 end
diff --git a/spec/models/repository_spec.rb b/spec/models/repository_spec.rb
index 2736541081eba2d0828b51ee697200ce07d3e104..45915dbaeeb8bf90fa51b2ae74aa7d1fba915d35 100644
--- a/spec/models/repository_spec.rb
+++ b/spec/models/repository_spec.rb
@@ -2019,19 +2019,43 @@ def create_remote_branch(remote_name, branch_name, target)
   end
 
   describe '#is_ancestor?' do
-    context 'Gitaly is_ancestor feature enabled' do
-      let(:commit) { repository.commit }
-      let(:ancestor) { commit.parents.first }
+    let(:commit) { repository.commit }
+    let(:ancestor) { commit.parents.first }
 
+    context 'with Gitaly enabled' do
+      it 'it is an ancestor' do
+        expect(repository.is_ancestor?(ancestor.id, commit.id)).to eq(true)
+      end
+
+      it 'it is not an ancestor' do
+        expect(repository.is_ancestor?(commit.id, ancestor.id)).to eq(false)
+      end
+
+      it 'returns false on nil-values' do
+        expect(repository.is_ancestor?(nil, commit.id)).to eq(false)
+        expect(repository.is_ancestor?(ancestor.id, nil)).to eq(false)
+        expect(repository.is_ancestor?(nil, nil)).to eq(false)
+      end
+    end
+
+    context 'with Gitaly disabled' do
       before do
-        allow(Gitlab::GitalyClient).to receive(:enabled?).and_return(true)
-        allow(Gitlab::GitalyClient).to receive(:feature_enabled?).with(:is_ancestor).and_return(true)
+        allow(Gitlab::GitalyClient).to receive(:enabled?).and_return(false)
+        allow(Gitlab::GitalyClient).to receive(:feature_enabled?).with(:is_ancestor).and_return(false)
       end
 
-      it "asks Gitaly server if it's an ancestor" do
-        expect_any_instance_of(Gitlab::GitalyClient::Commit).to receive(:is_ancestor).with(ancestor.id, commit.id)
+      it 'it is an ancestor' do
+        expect(repository.is_ancestor?(ancestor.id, commit.id)).to eq(true)
+      end
+
+      it 'it is not an ancestor' do
+        expect(repository.is_ancestor?(commit.id, ancestor.id)).to eq(false)
+      end
 
-        repository.is_ancestor?(ancestor.id, commit.id)
+      it 'returns false on nil-values' do
+        expect(repository.is_ancestor?(nil, commit.id)).to eq(false)
+        expect(repository.is_ancestor?(ancestor.id, nil)).to eq(false)
+        expect(repository.is_ancestor?(nil, nil)).to eq(false)
       end
     end
   end
diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb
index c829d045eb3c112e01735f4a6cd20177be26eac3..94e89af52cb6308e6674442eda7b32e9ad9ff3b3 100644
--- a/spec/models/user_spec.rb
+++ b/spec/models/user_spec.rb
@@ -1307,6 +1307,19 @@
     it { is_expected.to eq([private_group]) }
   end
 
+  describe '#groups_through_project_authorizations' do
+    it 'returns all groups being ancestor of the authorized project' do
+      user = create(:user)
+      group = create(:group, :private)
+      subgroup = create(:group, :private, parent: group)
+      subsubgroup = create(:group, :private, parent: subgroup)
+      project = create(:empty_project, :private, namespace: subsubgroup)
+      project.add_guest(user)
+
+      expect(user.groups_through_project_authorizations).to contain_exactly(group, subgroup, subsubgroup)
+    end
+  end
+
   describe '#authorized_projects', truncate: true do
     context 'with a minimum access level' do
       it 'includes projects for which the user is an owner' do
@@ -1949,6 +1962,34 @@ def add_user(access)
     end
   end
 
+  context '#invalidate_issue_cache_counts' do
+    let(:user) { build_stubbed(:user) }
+
+    it 'invalidates cache for issue counter' do
+      cache_mock = double
+
+      expect(cache_mock).to receive(:delete).with(['users', user.id, 'assigned_open_issues_count'])
+
+      allow(Rails).to receive(:cache).and_return(cache_mock)
+
+      user.invalidate_issue_cache_counts
+    end
+  end
+
+  context '#invalidate_merge_request_cache_counts' do
+    let(:user) { build_stubbed(:user) }
+
+    it 'invalidates cache for Merge Request counter' do
+      cache_mock = double
+
+      expect(cache_mock).to receive(:delete).with(['users', user.id, 'assigned_open_merge_requests_count'])
+
+      allow(Rails).to receive(:cache).and_return(cache_mock)
+
+      user.invalidate_merge_request_cache_counts
+    end
+  end
+
   describe '#forget_me!' do
     subject { create(:user, remember_created_at: Time.now) }
 
diff --git a/spec/serializers/build_entity_spec.rb b/spec/serializers/build_entity_spec.rb
index b5eb84ae43b840f5d19444de8d794ce7a43fa2a3..6d5e1046e86003f2b3b0b05a0b094862c219fc33 100644
--- a/spec/serializers/build_entity_spec.rb
+++ b/spec/serializers/build_entity_spec.rb
@@ -3,6 +3,7 @@
 describe BuildEntity do
   let(:user) { create(:user) }
   let(:build) { create(:ci_build) }
+  let(:project) { build.project }
   let(:request) { double('request') }
 
   before do
@@ -52,7 +53,10 @@
 
     context 'when user is allowed to trigger action' do
       before do
-        build.project.add_master(user)
+        project.add_developer(user)
+
+        create(:protected_branch, :developers_can_merge,
+               name: 'master', project: project)
       end
 
       it 'contains path to play action' do
diff --git a/spec/services/ci/play_build_service_spec.rb b/spec/services/ci/play_build_service_spec.rb
index d6f9fa42045b3c6cc5b2a2af01bde7bfc1a0a41f..ea211de1f82b8ee3ed5c21e423d532028f9c8bc5 100644
--- a/spec/services/ci/play_build_service_spec.rb
+++ b/spec/services/ci/play_build_service_spec.rb
@@ -13,8 +13,11 @@
   context 'when project does not have repository yet' do
     let(:project) { create(:empty_project) }
 
-    it 'allows user with master role to play build' do
-      project.add_master(user)
+    it 'allows user to play build if protected branch rules are met' do
+      project.add_developer(user)
+
+      create(:protected_branch, :developers_can_merge,
+             name: build.ref, project: project)
 
       service.execute(build)
 
@@ -45,7 +48,10 @@
     let(:build) { create(:ci_build, :manual, pipeline: pipeline) }
 
     before do
-      project.add_master(user)
+      project.add_developer(user)
+
+      create(:protected_branch, :developers_can_merge,
+             name: build.ref, project: project)
     end
 
     it 'enqueues the build' do
@@ -64,7 +70,10 @@
     let(:build) { create(:ci_build, when: :manual, pipeline: pipeline) }
 
     before do
-      project.add_master(user)
+      project.add_developer(user)
+
+      create(:protected_branch, :developers_can_merge,
+             name: build.ref, project: project)
     end
 
     it 'duplicates the build' do
diff --git a/spec/services/ci/process_pipeline_service_spec.rb b/spec/services/ci/process_pipeline_service_spec.rb
index fc5de5d069a196cbf10d03622e35b04b6f29f816..1557cb3c9381dd96dcd3cb2e536b45aa69527e71 100644
--- a/spec/services/ci/process_pipeline_service_spec.rb
+++ b/spec/services/ci/process_pipeline_service_spec.rb
@@ -333,10 +333,11 @@
 
     context 'when pipeline is promoted sequentially up to the end' do
       before do
-        # We are using create(:empty_project), and users has to be master in
-        # order to execute manual action when repository does not exist.
+        # Users need ability to merge into a branch in order to trigger
+        # protected manual actions.
         #
-        project.add_master(user)
+        create(:protected_branch, :developers_can_merge,
+               name: 'master', project: project)
       end
 
       it 'properly processes entire pipeline' do
diff --git a/spec/services/ci/retry_pipeline_service_spec.rb b/spec/services/ci/retry_pipeline_service_spec.rb
index d941d56c0d8f4756e0f6dd7aa2e3f04d448d2c26..3e860203063cecbe794ff83b86d5cbb26330eb04 100644
--- a/spec/services/ci/retry_pipeline_service_spec.rb
+++ b/spec/services/ci/retry_pipeline_service_spec.rb
@@ -6,9 +6,12 @@
   let(:pipeline) { create(:ci_pipeline, project: project) }
   let(:service) { described_class.new(project, user) }
 
-  context 'when user has ability to modify pipeline' do
+  context 'when user has full ability to modify pipeline' do
     before do
-      project.add_master(user)
+      project.add_developer(user)
+
+      create(:protected_branch, :developers_can_merge,
+             name: pipeline.ref, project: project)
     end
 
     context 'when there are already retried jobs present' do
diff --git a/spec/services/issuable/bulk_update_service_spec.rb b/spec/services/issuable/bulk_update_service_spec.rb
index 8fd5621475232f63f02ffcf8b814fdb9dedf622a..53c2675078912e842d065911f1937e8f943df210 100644
--- a/spec/services/issuable/bulk_update_service_spec.rb
+++ b/spec/services/issuable/bulk_update_service_spec.rb
@@ -72,7 +72,7 @@ def bulk_update(issuables, extra_params = {})
     end
 
     context "when the new assignee ID is #{IssuableFinder::NONE}" do
-      it "unassigns the issues" do
+      it 'unassigns the issues' do
         expect { bulk_update(merge_request, assignee_id: IssuableFinder::NONE) }
           .to change { merge_request.reload.assignee }.to(nil)
       end
diff --git a/spec/services/issues/close_service_spec.rb b/spec/services/issues/close_service_spec.rb
index 5184053171186d549976db34d8ea90478a0e2e4f..52f2066d9c5dc645f6b37fa01cdebe817f5891d5 100644
--- a/spec/services/issues/close_service_spec.rb
+++ b/spec/services/issues/close_service_spec.rb
@@ -41,6 +41,12 @@
 
       service.execute(issue)
     end
+
+    it 'invalidates counter cache for assignees' do
+      expect_any_instance_of(User).to receive(:invalidate_issue_cache_counts)
+
+      service.execute(issue)
+    end
   end
 
   describe '#close_issue' do
diff --git a/spec/services/issues/reopen_service_spec.rb b/spec/services/issues/reopen_service_spec.rb
index 93a8270fd16e477c97aab4842539e19eb76c032b..391ecad303a7e7722f7260c86cd218f76a9ea779 100644
--- a/spec/services/issues/reopen_service_spec.rb
+++ b/spec/services/issues/reopen_service_spec.rb
@@ -27,6 +27,13 @@
         project.team << [user, :master]
       end
 
+      it 'invalidates counter cache for assignees' do
+        issue.assignees << user
+        expect_any_instance_of(User).to receive(:invalidate_issue_cache_counts)
+
+        described_class.new(project, user).execute(issue)
+      end
+
       context 'when issue is not confidential' do
         it 'executes issue hooks' do
           expect(project).to receive(:execute_hooks).with(an_instance_of(Hash), :issue_hooks)
diff --git a/spec/services/merge_requests/close_service_spec.rb b/spec/services/merge_requests/close_service_spec.rb
index d55a7657c0ea2acb160ae58aa1c448ee427ce660..154f30aac3b4ab2ed2878eebf885553e9cf9aa15 100644
--- a/spec/services/merge_requests/close_service_spec.rb
+++ b/spec/services/merge_requests/close_service_spec.rb
@@ -15,6 +15,8 @@
   end
 
   describe '#execute' do
+    it_behaves_like 'cache counters invalidator'
+
     context 'valid params' do
       let(:service) { described_class.new(project, user, {}) }
 
diff --git a/spec/services/merge_requests/post_merge_service_spec.rb b/spec/services/merge_requests/post_merge_service_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..a20b32eda5f1aa90992463b1255a33d2293e4af7
--- /dev/null
+++ b/spec/services/merge_requests/post_merge_service_spec.rb
@@ -0,0 +1,15 @@
+require 'spec_helper'
+
+describe MergeRequests::PostMergeService, services: true do
+  let(:user) { create(:user) }
+  let(:merge_request) { create(:merge_request, assignee: user) }
+  let(:project) { merge_request.project }
+
+  before do
+    project.team << [user, :master]
+  end
+
+  describe '#execute' do
+    it_behaves_like 'cache counters invalidator'
+  end
+end
diff --git a/spec/services/merge_requests/reopen_service_spec.rb b/spec/services/merge_requests/reopen_service_spec.rb
index a99d4eac9bde5db6288496b3c8cc6597218029e0..b6d4db2f922e638e65b49a26966e7e0b5cd799cd 100644
--- a/spec/services/merge_requests/reopen_service_spec.rb
+++ b/spec/services/merge_requests/reopen_service_spec.rb
@@ -14,6 +14,8 @@
   end
 
   describe '#execute' do
+    it_behaves_like 'cache counters invalidator'
+
     context 'valid params' do
       let(:service) { described_class.new(project, user, {}) }
 
diff --git a/spec/services/notes/slash_commands_service_spec.rb b/spec/services/notes/slash_commands_service_spec.rb
index d837184382f5c2568784d22bd1e0499042151bdd..0edcc50ed7b22f347aa7d1866399d21ea890b240 100644
--- a/spec/services/notes/slash_commands_service_spec.rb
+++ b/spec/services/notes/slash_commands_service_spec.rb
@@ -220,31 +220,4 @@
       let(:note) { build(:note_on_commit, project: project) }
     end
   end
-
-  context 'CE restriction for issue assignees' do
-    describe '/assign' do
-      let(:project) { create(:empty_project) }
-      let(:master) { create(:user).tap { |u| project.team << [u, :master] } }
-      let(:assignee) { create(:user) }
-      let(:master) { create(:user) }
-      let(:service) { described_class.new(project, master) }
-      let(:note) { create(:note_on_issue, note: note_text, project: project) }
-
-      let(:note_text) do
-        %(/assign @#{assignee.username} @#{master.username}\n")
-      end
-
-      before do
-        project.team << [master, :master]
-        project.team << [assignee, :master]
-      end
-
-      it 'adds only one assignee from the list' do
-        _, command_params = service.extract_commands(note)
-        service.execute(command_params, note)
-
-        expect(note.noteable.assignees.count).to eq(2)
-      end
-    end
-  end
 end
diff --git a/spec/services/slash_commands/interpret_service_spec.rb b/spec/services/slash_commands/interpret_service_spec.rb
index 40427fc2173d9f58d1b94acc803b6f51107977ba..553511328b751311452c602545b593f67a24c80c 100644
--- a/spec/services/slash_commands/interpret_service_spec.rb
+++ b/spec/services/slash_commands/interpret_service_spec.rb
@@ -401,7 +401,7 @@
         it 'fetches assignee and populates assignee_id if content contains /assign' do
           _, updates = service.execute(content, issue)
 
-          expect(updates[:assignee_ids]).to match_array([developer.id, developer2.id])
+          expect(updates[:assignee_ids]).to match_array([developer.id])
         end
       end
 
diff --git a/spec/support/issuable_shared_examples.rb b/spec/support/issuable_shared_examples.rb
new file mode 100644
index 0000000000000000000000000000000000000000..03011535351e0c1c56b11080eb20ed6afa613e78
--- /dev/null
+++ b/spec/support/issuable_shared_examples.rb
@@ -0,0 +1,7 @@
+shared_examples 'cache counters invalidator' do
+  it 'invalidates counter cache for assignees' do
+    expect_any_instance_of(User).to receive(:invalidate_merge_request_cache_counts)
+
+    described_class.new(project, user, {}).execute(merge_request)
+  end
+end
diff --git a/spec/uploaders/lfs_object_uploader_spec.rb b/spec/uploaders/lfs_object_uploader_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..c3b72e7d677e39c01c824794a5f1d85ec033d087
--- /dev/null
+++ b/spec/uploaders/lfs_object_uploader_spec.rb
@@ -0,0 +1,31 @@
+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
+
+  describe '#move_to_cache' do
+    it 'is true' do
+      expect(uploader.move_to_cache).to eq(true)
+    end
+  end
+
+  describe '#move_to_store' do
+    it 'is true' do
+      expect(uploader.move_to_store).to eq(true)
+    end
+  end
+end