diff --git a/Gemfile b/Gemfile
index f27d6363e3d5a68913b494a6cc0f7e62f7644075..2cc7764e6b8f048d3338790e3611e3bb14994ae6 100644
--- a/Gemfile
+++ b/Gemfile
@@ -132,7 +132,7 @@ gem 'after_commit_queue', '~> 1.3.0'
 gem 'acts-as-taggable-on', '~> 4.0'
 
 # Background jobs
-gem 'sidekiq', '~> 4.2'
+gem 'sidekiq', '~> 4.2.7'
 gem 'sidekiq-cron', '~> 0.4.4'
 gem 'redis-namespace', '~> 1.5.2'
 gem 'sidekiq-limit_fetch', '~> 3.4'
diff --git a/Gemfile.lock b/Gemfile.lock
index c464ff70587ad80ec6be9d43603780d52928dadf..3de1a7cbf262178febf56fcfbfaee170cd7cd9a3 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -126,7 +126,7 @@ GEM
     coffee-script-source (1.10.0)
     colorize (0.7.7)
     concurrent-ruby (1.0.2)
-    connection_pool (2.2.0)
+    connection_pool (2.2.1)
     crack (0.4.3)
       safe_yaml (~> 1.0.0)
     creole (0.5.0)
@@ -648,10 +648,10 @@ GEM
       rack
     shoulda-matchers (2.8.0)
       activesupport (>= 3.0.0)
-    sidekiq (4.2.1)
+    sidekiq (4.2.7)
       concurrent-ruby (~> 1.0)
       connection_pool (~> 2.2, >= 2.2.0)
-      rack-protection (~> 1.5)
+      rack-protection (>= 1.5.0)
       redis (~> 3.2, >= 3.2.1)
     sidekiq-cron (0.4.4)
       redis-namespace (>= 1.5.2)
@@ -928,7 +928,7 @@ DEPENDENCIES
   settingslogic (~> 2.0.9)
   sham_rack (~> 1.3.6)
   shoulda-matchers (~> 2.8.0)
-  sidekiq (~> 4.2)
+  sidekiq (~> 4.2.7)
   sidekiq-cron (~> 0.4.4)
   sidekiq-limit_fetch (~> 3.4)
   simplecov (= 0.12.0)
diff --git a/app/assets/javascripts/commit/file.js b/app/assets/javascripts/commit/file.js
index 3f29826fa9b9887216c84001e6148f60043dfd54..600bac8834a0a71ded3a5a155421b6e57ed3436e 100644
--- a/app/assets/javascripts/commit/file.js
+++ b/app/assets/javascripts/commit/file.js
@@ -3,7 +3,7 @@
   this.CommitFile = (function() {
     function CommitFile(file) {
       if ($('.image', file).length) {
-        new ImageFile(file);
+        new gl.ImageFile(file);
       }
     }
 
diff --git a/app/assets/javascripts/commit/image_file.js b/app/assets/javascripts/commit/image_file.js
index 4c2ae5953194c05235737579101324e62543ae97..fd8910e916f368efff717adbf5babc79c0775e93 100644
--- a/app/assets/javascripts/commit/image_file.js
+++ b/app/assets/javascripts/commit/image_file.js
@@ -1,6 +1,6 @@
 /* eslint-disable func-names, space-before-function-paren, wrap-iife, no-var, no-use-before-define, prefer-arrow-callback, no-else-return, consistent-return, prefer-template, quotes, one-var, one-var-declaration-per-line, no-unused-vars, no-return-assign, comma-dangle, quote-props, no-unused-expressions, no-sequences, object-shorthand, padded-blocks, max-len */
 (function() {
-  this.ImageFile = (function() {
+  gl.ImageFile = (function() {
     var prepareFrames;
 
     // Width where images must fits in, for 2-up this gets divided by 2
diff --git a/app/assets/javascripts/copy_to_clipboard.js b/app/assets/javascripts/copy_to_clipboard.js
index 1cc34e490c2e2856ddecc6f79297e44a293b2741..efa228a75d90dd4824052ddd9e0a98b575583ff2 100644
--- a/app/assets/javascripts/copy_to_clipboard.js
+++ b/app/assets/javascripts/copy_to_clipboard.js
@@ -6,7 +6,7 @@
   var genericError, genericSuccess, showTooltip;
 
   genericSuccess = function(e) {
-    showTooltip(e.trigger, 'Copied!');
+    showTooltip(e.trigger, 'Copied');
     // Clear the selection and blur the trigger so it loses its border
     e.clearSelection();
     return $(e.trigger).blur();
@@ -31,7 +31,7 @@
     var originalTitle = $target.data('original-title');
 
     $target
-      .attr('title', 'Copied!')
+      .attr('title', 'Copied')
       .tooltip('fixTitle')
       .tooltip('show')
       .attr('title', originalTitle)
diff --git a/app/assets/javascripts/diff.js.es6 b/app/assets/javascripts/diff.js.es6
index ecf9d1de81c712b69a16fc3fa8f53e22590db2eb..9cf33e629589eb3d6b23effcd0c17017c220c4c6 100644
--- a/app/assets/javascripts/diff.js.es6
+++ b/app/assets/javascripts/diff.js.es6
@@ -5,8 +5,11 @@
 
   class Diff {
     constructor() {
-      $('.files .diff-file').singleFileDiff();
-      $('.files .diff-file').filesCommentButton();
+      const $diffFile = $('.files .diff-file');
+      $diffFile.singleFileDiff();
+      $diffFile.filesCommentButton();
+
+      $diffFile.each((index, file) => new gl.ImageFile(file));
 
       if (this.diffViewType() === 'parallel') {
         $('.content-wrapper .container-fluid').removeClass('container-limited');
diff --git a/app/assets/javascripts/gfm_auto_complete.js.es6 b/app/assets/javascripts/gfm_auto_complete.js.es6
index 6f9d62830710e00559cedbd248d612b6e2a4a411..2f3da7451194cf472a89632fd3c34fa24271e6f8 100644
--- a/app/assets/javascripts/gfm_auto_complete.js.es6
+++ b/app/assets/javascripts/gfm_auto_complete.js.es6
@@ -52,6 +52,10 @@
         return $.fn.atwho["default"].callbacks.filter(query, data, searchKey);
       },
       beforeInsert: function(value) {
+        if (value && !this.setting.skipSpecialCharacterTest) {
+          var withoutAt = value.substring(1);
+          if (withoutAt && /[^\w\d]/.test(withoutAt)) value = value.charAt() + '"' + withoutAt + '"';
+        }
         if (!GitLab.GfmAutoComplete.dataLoaded) {
           return this.at;
         } else {
@@ -117,6 +121,7 @@
         insertTpl: ':${name}:',
         data: ['loading'],
         startWithSpace: false,
+        skipSpecialCharacterTest: true,
         callbacks: {
           sorter: this.DefaultOptions.sorter,
           filter: this.DefaultOptions.filter,
@@ -141,6 +146,7 @@
         data: ['loading'],
         startWithSpace: false,
         alwaysHighlightFirst: true,
+        skipSpecialCharacterTest: true,
         callbacks: {
           sorter: this.DefaultOptions.sorter,
           filter: this.DefaultOptions.filter,
@@ -219,12 +225,13 @@
             }
           };
         })(this),
-        insertTpl: '${atwho-at}"${title}"',
+        insertTpl: '${atwho-at}${title}',
         data: ['loading'],
         startWithSpace: false,
         callbacks: {
           matcher: this.DefaultOptions.matcher,
           sorter: this.DefaultOptions.sorter,
+          beforeInsert: this.DefaultOptions.beforeInsert,
           beforeSave: function(milestones) {
             return $.map(milestones, function(m) {
               if (m.title == null) {
@@ -284,18 +291,11 @@
         callbacks: {
           matcher: this.DefaultOptions.matcher,
           sorter: this.DefaultOptions.sorter,
+          beforeInsert: this.DefaultOptions.beforeInsert,
           beforeSave: function(merges) {
-            var sanitizeLabelTitle;
-            sanitizeLabelTitle = function(title) {
-              if (/[\w\?&]+\s+[\w\?&]+/g.test(title)) {
-                return "\"" + (sanitize(title)) + "\"";
-              } else {
-                return sanitize(title);
-              }
-            };
             return $.map(merges, function(m) {
               return {
-                title: sanitizeLabelTitle(m.title),
+                title: sanitize(m.title),
                 color: m.color,
                 search: "" + m.title
               };
@@ -308,6 +308,7 @@
         at: '/',
         alias: 'commands',
         searchKey: 'search',
+        skipSpecialCharacterTest: true,
         displayTpl: function(value) {
           var tpl = '<li>/${name}';
           if (value.aliases.length > 0) {
diff --git a/app/assets/javascripts/graphs/stat_graph_contributors_util.js b/app/assets/javascripts/graphs/stat_graph_contributors_util.js
index 051ff98c774c0e8b27e7f51ff147d67da6cf3b70..1982f4af9399a76162e5bc92588cd6f8e4b28206 100644
--- a/app/assets/javascripts/graphs/stat_graph_contributors_util.js
+++ b/app/assets/javascripts/graphs/stat_graph_contributors_util.js
@@ -2,7 +2,7 @@
 (function() {
   window.ContributorsStatGraphUtil = {
     parse_log: function(log) {
-      var by_author, by_email, data, entry, i, len, total;
+      var by_author, by_email, data, entry, i, len, total, normalized_email;
       total = {};
       by_author = {};
       by_email = {};
@@ -11,7 +11,8 @@
         if (total[entry.date] == null) {
           this.add_date(entry.date, total);
         }
-        data = by_author[entry.author_name] || by_email[entry.author_email];
+        normalized_email = entry.author_email.toLowerCase();
+        data = by_author[entry.author_name] || by_email[normalized_email];
         if (data == null) {
           data = this.add_author(entry, by_author, by_email);
         }
@@ -32,12 +33,14 @@
       return collection[date].date = date;
     },
     add_author: function(author, by_author, by_email) {
-      var data;
+      var data, normalized_email;
       data = {};
       data.author_name = author.author_name;
       data.author_email = author.author_email;
+      normalized_email = author.author_email.toLowerCase();
       by_author[author.author_name] = data;
-      return by_email[author.author_email] = data;
+      by_email[normalized_email] = data;
+      return data;
     },
     store_data: function(entry, total, by_author) {
       this.store_commits(total, by_author);
diff --git a/app/assets/javascripts/merge_request_widget.js.es6 b/app/assets/javascripts/merge_request_widget.js.es6
index d9495e503888ac4af03e7a0c5a724c3110bc8b6c..7022aa1263b8ee55b57c2dd12c754c137d9316c3 100644
--- a/app/assets/javascripts/merge_request_widget.js.es6
+++ b/app/assets/javascripts/merge_request_widget.js.es6
@@ -40,19 +40,26 @@
       $('#modal_merge_info').modal({
         show: false
       });
-      this.firstCICheck = true;
-      this.readyForCICheck = false;
-      this.readyForCIEnvironmentCheck = false;
-      this.cancel = false;
-      clearInterval(this.fetchBuildStatusInterval);
-      clearInterval(this.fetchBuildEnvironmentStatusInterval);
       this.clearEventListeners();
       this.addEventListeners();
       this.getCIStatus(false);
-      this.getCIEnvironmentsStatus();
       this.retrieveSuccessIcon();
-      this.pollCIStatus();
-      this.pollCIEnvironmentsStatus();
+
+      this.ciStatusInterval = new global.SmartInterval({
+        callback: this.getCIStatus.bind(this, true),
+        startingInterval: 10000,
+        maxInterval: 30000,
+        hiddenInterval: 120000,
+        incrementByFactorOf: 5000,
+      });
+      this.ciEnvironmentStatusInterval = new global.SmartInterval({
+        callback: this.getCIEnvironmentsStatus.bind(this),
+        startingInterval: 30000,
+        maxInterval: 120000,
+        hiddenInterval: 240000,
+        incrementByFactorOf: 15000,
+        immediateExecution: true,
+      });
       notifyPermissions();
     }
 
@@ -60,10 +67,6 @@
       return $(document).off('page:change.merge_request');
     };
 
-    MergeRequestWidget.prototype.cancelPolling = function() {
-      return this.cancel = true;
-    };
-
     MergeRequestWidget.prototype.addEventListeners = function() {
       var allowedPages;
       allowedPages = ['show', 'commits', 'builds', 'pipelines', 'changes'];
@@ -72,9 +75,6 @@
           var page;
           page = $('body').data('page').split(':').last();
           if (allowedPages.indexOf(page) < 0) {
-            clearInterval(_this.fetchBuildStatusInterval);
-            clearInterval(_this.fetchBuildEnvironmentStatusInterval);
-            _this.cancelPolling();
             return _this.clearEventListeners();
           }
         };
@@ -114,6 +114,11 @@
       });
     };
 
+    MergeRequestWidget.prototype.cancelPolling = function () {
+      this.ciStatusInterval.cancel();
+      this.ciEnvironmentStatusInterval.cancel();
+    };
+
     MergeRequestWidget.prototype.getMergeStatus = function() {
       return $.get(this.opts.merge_check_url, function(data) {
         return $('.mr-state-widget').replaceWith(data);
@@ -131,18 +136,6 @@
       }
     };
 
-    MergeRequestWidget.prototype.pollCIStatus = function() {
-      return this.fetchBuildStatusInterval = setInterval(((function(_this) {
-        return function() {
-          if (!_this.readyForCICheck) {
-            return;
-          }
-          _this.getCIStatus(true);
-          return _this.readyForCICheck = false;
-        };
-      })(this)), 10000);
-    };
-
     MergeRequestWidget.prototype.getCIStatus = function(showNotification) {
       var _this;
       _this = this;
@@ -150,23 +143,17 @@
       return $.getJSON(this.opts.ci_status_url, (function(_this) {
         return function(data) {
           var message, status, title;
-          if (_this.cancel) {
-            return;
-          }
-          _this.readyForCICheck = true;
           if (data.status === '') {
             return;
           }
           if (data.environments && data.environments.length) _this.renderEnvironments(data.environments);
-          if (_this.firstCICheck || data.status !== _this.opts.ci_status && (data.status != null)) {
+          if (data.status !== _this.opts.ci_status && (data.status != null)) {
             _this.opts.ci_status = data.status;
             _this.showCIStatus(data.status);
             if (data.coverage) {
               _this.showCICoverage(data.coverage);
             }
-            // The first check should only update the UI, a notification
-            // should only be displayed on status changes
-            if (showNotification && !_this.firstCICheck) {
+            if (showNotification) {
               status = _this.ciLabelForStatus(data.status);
               if (status === "preparing") {
                 title = _this.opts.ci_title.preparing;
@@ -184,24 +171,13 @@
                 return Turbolinks.visit(_this.opts.builds_path);
               });
             }
-            return _this.firstCICheck = false;
           }
         };
       })(this));
     };
 
-    MergeRequestWidget.prototype.pollCIEnvironmentsStatus = function() {
-      this.fetchBuildEnvironmentStatusInterval = setInterval(() => {
-        if (!this.readyForCIEnvironmentCheck) return;
-        this.getCIEnvironmentsStatus();
-        this.readyForCIEnvironmentCheck = false;
-      }, 300000);
-    };
-
     MergeRequestWidget.prototype.getCIEnvironmentsStatus = function() {
       $.getJSON(this.opts.ci_environments_status_url, (environments) => {
-        if (this.cancel) return;
-        this.readyForCIEnvironmentCheck = true;
         if (environments && environments.length) this.renderEnvironments(environments);
       });
     };
@@ -212,11 +188,11 @@
         if ($(`.mr-state-widget #${ environment.id }`).length) return;
         const $template = $(DEPLOYMENT_TEMPLATE);
         if (!environment.external_url || !environment.external_url_formatted) $('.js-environment-link', $template).remove();
-        
+
         if (!environment.stop_url) {
           $('.js-stop-env-link', $template).remove();
         }
-        
+
         if (environment.deployed_at && environment.deployed_at_formatted) {
           environment.deployed_at = gl.utils.getTimeago().format(environment.deployed_at, 'gl_en') + '.';
         } else {
diff --git a/app/assets/javascripts/pipelines.js.es6 b/app/assets/javascripts/pipelines.js.es6
index 72c6c4a1fcd393847262ee7322a8cec5a9a109ab..fb95648e1c74cf5432796f484fc461215ade89c8 100644
--- a/app/assets/javascripts/pipelines.js.es6
+++ b/app/assets/javascripts/pipelines.js.es6
@@ -4,7 +4,7 @@
 ((global) => {
 
   class Pipelines {
-    constructor(options) {
+    constructor(options = {}) {
 
       if (options.initTabs && options.tabsOptions) {
         new global.LinkedTabs(options.tabsOptions);
@@ -14,9 +14,11 @@
     }
 
     addMarginToBuildColumns() {
-      this.pipelineGraph = document.querySelector('.pipeline-graph');
-      const secondChildBuildNodes = document.querySelector('.pipeline-graph').querySelectorAll('.build:nth-child(2)');
-      for (buildNodeIndex in secondChildBuildNodes) {
+      this.pipelineGraph = document.querySelector('.js-pipeline-graph');
+
+      const secondChildBuildNodes = this.pipelineGraph.querySelectorAll('.build:nth-child(2)');
+
+      for (const buildNodeIndex in secondChildBuildNodes) {
         const buildNode = secondChildBuildNodes[buildNodeIndex];
         const firstChildBuildNode = buildNode.previousElementSibling;
         if (!firstChildBuildNode || !firstChildBuildNode.matches('.build')) continue;
@@ -28,6 +30,7 @@
         const columnBuilds = previousColumn.querySelectorAll('.build');
         if (columnBuilds.length === 1) previousColumn.classList.add('no-margin');
       }
+
       this.pipelineGraph.classList.remove('hidden');
     }
   }
diff --git a/app/assets/javascripts/smart_interval.js.es6 b/app/assets/javascripts/smart_interval.js.es6
index 5eb15dba79b78eed41a4c21e2e19fca986554584..40f67637c7c8f6f5a570840a531500d09ec028e4 100644
--- a/app/assets/javascripts/smart_interval.js.es6
+++ b/app/assets/javascripts/smart_interval.js.es6
@@ -7,24 +7,31 @@
 (() => {
   class SmartInterval {
     /**
-      * @param { function } callback Function to be called on each iteration (required)
-      * @param { milliseconds } startingInterval `currentInterval` is set to this initially
-      * @param { milliseconds } maxInterval `currentInterval` will be incremented to this
-      * @param { integer } incrementByFactorOf `currentInterval` is incremented by this factor
-      * @param { boolean } lazyStart Configure if timer is initialized on instantiation or lazily
+      * @param { function } opts.callback Function to be called on each iteration (required)
+      * @param { milliseconds } opts.startingInterval `currentInterval` is set to this initially
+      * @param { milliseconds } opts.maxInterval `currentInterval` will be incremented to this
+      * @param { milliseconds } opts.hiddenInterval `currentInterval` is set to this
+      *                         when the page is hidden
+      * @param { integer } opts.incrementByFactorOf `currentInterval` is incremented by this factor
+      * @param { boolean } opts.lazyStart Configure if timer is initialized on
+      *                    instantiation or lazily
+      * @param { boolean } opts.immediateExecution Configure if callback should
+      *                    be executed before the first interval.
       */
-    constructor({ callback, startingInterval, maxInterval, incrementByFactorOf, lazyStart }) {
+    constructor(opts = {}) {
       this.cfg = {
-        callback,
-        startingInterval,
-        maxInterval,
-        incrementByFactorOf,
-        lazyStart,
+        callback: opts.callback,
+        startingInterval: opts.startingInterval,
+        maxInterval: opts.maxInterval,
+        hiddenInterval: opts.hiddenInterval,
+        incrementByFactorOf: opts.incrementByFactorOf,
+        lazyStart: opts.lazyStart,
+        immediateExecution: opts.immediateExecution,
       };
 
       this.state = {
         intervalId: null,
-        currentInterval: startingInterval,
+        currentInterval: this.cfg.startingInterval,
         pageVisibility: 'visible',
       };
 
@@ -36,6 +43,11 @@
       const cfg = this.cfg;
       const state = this.state;
 
+      if (cfg.immediateExecution) {
+        cfg.immediateExecution = false;
+        cfg.callback();
+      }
+
       state.intervalId = window.setInterval(() => {
         cfg.callback();
 
@@ -54,14 +66,29 @@
       this.stopTimer();
     }
 
+    onVisibilityHidden() {
+      if (this.cfg.hiddenInterval) {
+        this.setCurrentInterval(this.cfg.hiddenInterval);
+        this.resume();
+      } else {
+        this.cancel();
+      }
+    }
+
     // start a timer, using the existing interval
     resume() {
       this.stopTimer(); // stop exsiting timer, in case timer was not previously stopped
       this.start();
     }
 
+    onVisibilityVisible() {
+      this.cancel();
+      this.start();
+    }
+
     destroy() {
       this.cancel();
+      document.removeEventListener('visibilitychange', this.handleVisibilityChange);
       $(document).off('visibilitychange').off('page:before-unload');
     }
 
@@ -80,11 +107,7 @@
 
     initVisibilityChangeHandling() {
       // cancel interval when tab no longer shown (prevents cached pages from polling)
-      $(document)
-        .off('visibilitychange').on('visibilitychange', (e) => {
-          this.state.pageVisibility = e.target.visibilityState;
-          this.handleVisibilityChange();
-        });
+      document.addEventListener('visibilitychange', this.handleVisibilityChange.bind(this));
     }
 
     initPageUnloadHandling() {
@@ -92,10 +115,11 @@
       $(document).on('page:before-unload', () => this.cancel());
     }
 
-    handleVisibilityChange() {
-      const state = this.state;
-
-      const intervalAction = state.pageVisibility === 'hidden' ? this.cancel : this.resume;
+    handleVisibilityChange(e) {
+      this.state.pageVisibility = e.target.visibilityState;
+      const intervalAction = this.isPageVisible() ?
+        this.onVisibilityVisible :
+        this.onVisibilityHidden;
 
       intervalAction.apply(this);
     }
@@ -111,6 +135,7 @@
     incrementInterval() {
       const cfg = this.cfg;
       const currentInterval = this.getCurrentInterval();
+      if (cfg.hiddenInterval && !this.isPageVisible()) return;
       let nextInterval = currentInterval * cfg.incrementByFactorOf;
 
       if (nextInterval > cfg.maxInterval) {
@@ -120,6 +145,8 @@
       this.setCurrentInterval(nextInterval);
     }
 
+    isPageVisible() { return this.state.pageVisibility === 'visible'; }
+
     stopTimer() {
       const state = this.state;
 
diff --git a/app/assets/stylesheets/framework/variables.scss b/app/assets/stylesheets/framework/variables.scss
index 18716813c48c10b0b6e44a763accb49a1d51bb2b..a1d5f6427f4365628604da107cb7b013c6224b3c 100644
--- a/app/assets/stylesheets/framework/variables.scss
+++ b/app/assets/stylesheets/framework/variables.scss
@@ -486,6 +486,7 @@ $jq-ui-default-color: #777;
 $label-gray-bg: #f8fafc;
 $label-inverse-bg: #333;
 $label-remove-border: rgba(0, 0, 0, .1);
+$label-border-radius: 14px;
 
 /*
 * Lint
diff --git a/app/assets/stylesheets/pages/environments.scss b/app/assets/stylesheets/pages/environments.scss
index de3d2ba549fae5a2c5723667981a6bfec74c0b77..e716f24c8e295ace650af5940abbb5c9189e7446 100644
--- a/app/assets/stylesheets/pages/environments.scss
+++ b/app/assets/stylesheets/pages/environments.scss
@@ -1,6 +1,8 @@
-.deployments-container {
-  width: 100%;
-  overflow: auto;
+@media (max-width: $screen-md-max) {
+  .deployments-container {
+    width: 100%;
+    overflow: auto;
+  }
 }
 
 .environments-list-loading {
diff --git a/app/assets/stylesheets/pages/issuable.scss b/app/assets/stylesheets/pages/issuable.scss
index 90587b9425b563f6b30a8c4c9ef01ef202ea7892..407c0afbac8d4e66270ce3bf45e8db54ed032879 100644
--- a/app/assets/stylesheets/pages/issuable.scss
+++ b/app/assets/stylesheets/pages/issuable.scss
@@ -30,6 +30,7 @@
 
     .color-label {
       padding: 6px 10px;
+      border-radius: $label-border-radius;
     }
   }
 
diff --git a/app/assets/stylesheets/pages/labels.scss b/app/assets/stylesheets/pages/labels.scss
index b1ccd6444509fabf3ccb137efdeefb492577054c..25c91203ff48808ec8d677f749c4f45d168766ab 100644
--- a/app/assets/stylesheets/pages/labels.scss
+++ b/app/assets/stylesheets/pages/labels.scss
@@ -104,7 +104,8 @@
 }
 
 .color-label {
-  padding: 3px 4px;
+  padding: 3px 7px;
+  border-radius: $label-border-radius;
 }
 
 .dropdown-labels-error {
diff --git a/app/assets/stylesheets/pages/notifications.scss b/app/assets/stylesheets/pages/notifications.scss
index 94fbbef3c778868d380f6d84c68ea6a4a67b3ca1..7d61390a439915cebad2313c3a223864d77632ed 100644
--- a/app/assets/stylesheets/pages/notifications.scss
+++ b/app/assets/stylesheets/pages/notifications.scss
@@ -1,5 +1,9 @@
 .notification-list-item {
   line-height: 34px;
+
+  .dropdown-menu {
+    @extend .dropdown-menu-align-right;
+  }
 }
 
 .notification {
diff --git a/app/assets/stylesheets/pages/pipelines.scss b/app/assets/stylesheets/pages/pipelines.scss
index 08062b855049398ed9bde92f7572a0634b380b2b..6822f916cc5b7b882ae7becf928d918895ef532a 100644
--- a/app/assets/stylesheets/pages/pipelines.scss
+++ b/app/assets/stylesheets/pages/pipelines.scss
@@ -37,12 +37,13 @@
   }
 }
 
-.content-list {
-
-  &.pipelines,
-  &.builds-content-list {
-    width: 100%;
-    overflow: auto;
+@media (max-width: $screen-md-max) {
+  .content-list {
+    &.pipelines,
+    &.builds-content-list {
+      width: 100%;
+      overflow: auto;
+    }
   }
 }
 
@@ -666,10 +667,6 @@
       min-width: 900px;
     }
 
-    .content-list.pipelines {
-      overflow: auto;
-    }
-
     .stage {
       max-width: 100px;
       width: 100px;
diff --git a/app/assets/stylesheets/pages/projects.scss b/app/assets/stylesheets/pages/projects.scss
index 72b6685d9401818186354ee00bff27c5f595fa21..6e0f6b1cd8130277e2abb4303387d6f93e1f35dd 100644
--- a/app/assets/stylesheets/pages/projects.scss
+++ b/app/assets/stylesheets/pages/projects.scss
@@ -188,6 +188,10 @@
     margin-left: 10px;
   }
 
+  .notification-dropdown .dropdown-menu {
+    @extend .dropdown-menu-align-right;
+  }
+
   .download-button {
     @media (max-width: $screen-md-max) {
       margin-left: 0;
diff --git a/app/assets/stylesheets/pages/snippets.scss b/app/assets/stylesheets/pages/snippets.scss
index 857eb76131a9696c20a576b1b5f9e2bd7b91b368..ff13b86acf0da338756afe9fc8b728b12aaf211c 100644
--- a/app/assets/stylesheets/pages/snippets.scss
+++ b/app/assets/stylesheets/pages/snippets.scss
@@ -1,3 +1,13 @@
+.snippet-row {
+  .title {
+    margin-bottom: 2px;
+  }
+
+  .snippet-filename {
+    padding: 0 2px;
+  }
+}
+
 .snippet-form-holder .file-holder .file-title {
   padding: 2px;
 }
@@ -24,11 +34,17 @@
   padding-bottom: $gl-padding;
 }
 
+.snippet-header {
+  padding: $gl-padding 0;
+}
+
 .snippet-title {
   font-size: 24px;
   font-weight: 600;
-  padding: $gl-padding;
-  padding-left: 0;
+}
+
+.snippet-edited-ago {
+  color: $gray-darkest;
 }
 
 .snippet-actions {
diff --git a/app/controllers/projects/releases_controller.rb b/app/controllers/projects/releases_controller.rb
index 0825a4311cbb62b5988847114566e7b2f4d64ff9..2c097cb4d8d7c622007db8f2d73be16e95137e97 100644
--- a/app/controllers/projects/releases_controller.rb
+++ b/app/controllers/projects/releases_controller.rb
@@ -10,7 +10,14 @@ class Projects::ReleasesController < Projects::ApplicationController
   end
 
   def update
-    release.update_attributes(release_params)
+    # Release belongs to Tag which is not active record object,
+    # it exists only to save a description to each Tag.
+    # If description is empty we should destroy the existing record.
+    if release_params[:description].present?
+      release.update_attributes(release_params)
+    else
+      release.destroy
+    end
 
     redirect_to namespace_project_tag_path(@project.namespace, @project, @tag.name)
   end
diff --git a/app/controllers/projects/snippets_controller.rb b/app/controllers/projects/snippets_controller.rb
index e290a0eadda814488d8a3893abbe7f43e3a87f0b..0720be2e55d15ea3dd17e68e56476f2897d83933 100644
--- a/app/controllers/projects/snippets_controller.rb
+++ b/app/controllers/projects/snippets_controller.rb
@@ -19,10 +19,12 @@ class Projects::SnippetsController < Projects::ApplicationController
   respond_to :html
 
   def index
-    @snippets = SnippetsFinder.new.execute(current_user, {
+    @snippets = SnippetsFinder.new.execute(
+      current_user,
       filter: :by_project,
-      project: @project
-    })
+      project: @project,
+      scope: params[:scope]
+    )
     @snippets = @snippets.page(params[:page])
   end
 
diff --git a/app/controllers/sessions_controller.rb b/app/controllers/sessions_controller.rb
index 38e7c6f4a4859e4bd9fe062e1b0c7cd53eec6a35..8c698695202681347dcc3e332a3df2c1929a7f4d 100644
--- a/app/controllers/sessions_controller.rb
+++ b/app/controllers/sessions_controller.rb
@@ -37,6 +37,12 @@ class SessionsController < Devise::SessionsController
     end
   end
 
+  def destroy
+    super
+    # hide the signed_out notice
+    flash[:notice] = nil
+  end
+
   private
 
   # Handle an "initial setup" state, where there's only one user, it's an admin,
diff --git a/app/finders/snippets_finder.rb b/app/finders/snippets_finder.rb
index 00ff161103932ede759303894fecd0cbda80d564..da6e6e87a6ff336cae9660a6415543220c449f1f 100644
--- a/app/finders/snippets_finder.rb
+++ b/app/finders/snippets_finder.rb
@@ -1,14 +1,17 @@
 class SnippetsFinder
   def execute(current_user, params = {})
     filter = params[:filter]
+    user = params.fetch(:user, current_user)
 
     case filter
     when :all then
       snippets(current_user).fresh
+    when :public then
+      Snippet.are_public.fresh
     when :by_user then
-      by_user(current_user, params[:user], params[:scope])
+      by_user(current_user, user, params[:scope])
     when :by_project
-      by_project(current_user, params[:project])
+      by_project(current_user, params[:project], params[:scope])
     end
   end
 
@@ -29,35 +32,35 @@ class SnippetsFinder
   def by_user(current_user, user, scope)
     snippets = user.snippets.fresh
 
-    return snippets.are_public unless current_user
-
-    if user == current_user
-      case scope
-      when 'are_internal' then
-        snippets.are_internal
-      when 'are_private' then
-        snippets.are_private
-      when 'are_public' then
-        snippets.are_public
-      else
-        snippets
-      end
+    if current_user
+      include_private = user == current_user
+      by_scope(snippets, scope, include_private)
     else
-      snippets.public_and_internal
+      snippets.are_public
     end
   end
 
-  def by_project(current_user, project)
+  def by_project(current_user, project, scope)
     snippets = project.snippets.fresh
 
     if current_user
-      if project.team.member?(current_user) || current_user.admin?
-        snippets
-      else
-        snippets.public_and_internal
-      end
+      include_private = project.team.member?(current_user) || current_user.admin?
+      by_scope(snippets, scope, include_private)
     else
       snippets.are_public
     end
   end
+
+  def by_scope(snippets, scope = nil, include_private = false)
+    case scope.to_s
+    when 'are_private'
+      include_private ? snippets.are_private : Snippet.none
+    when 'are_internal'
+      snippets.are_internal
+    when 'are_public'
+      snippets.are_public
+    else
+      include_private ? snippets : snippets.public_and_internal
+    end
+  end
 end
diff --git a/app/helpers/button_helper.rb b/app/helpers/button_helper.rb
index dee3c78df47a730b08b51d87d5737e81ba47c0f3..4c7c16d694c624c866b2e0ac009620f11b2c2ef2 100644
--- a/app/helpers/button_helper.rb
+++ b/app/helpers/button_helper.rb
@@ -16,7 +16,7 @@ module ButtonHelper
   # See http://clipboardjs.com/#usage
   def clipboard_button(data = {})
     css_class = data[:class] || 'btn-clipboard btn-transparent'
-    title = data[:title] || 'Copy to Clipboard'
+    title = data[:title] || 'Copy to clipboard'
     data = { toggle: 'tooltip', placement: 'bottom', container: 'body' }.merge(data)
     content_tag :button,
       icon('clipboard'),
diff --git a/app/helpers/ci_status_helper.rb b/app/helpers/ci_status_helper.rb
index 8e19752a8a1142565450fe635345ca277e52cd70..d9f5e01f0dcb0a9a8a11c73e228fb8e336b27957 100644
--- a/app/helpers/ci_status_helper.rb
+++ b/app/helpers/ci_status_helper.rb
@@ -4,25 +4,7 @@ module CiStatusHelper
     builds_namespace_project_commit_path(project.namespace, project, pipeline.sha)
   end
 
-  def ci_status_with_icon(status, target = nil)
-    content = ci_icon_for_status(status) + ci_text_for_status(status)
-    klass = "ci-status ci-#{status}"
-
-    if target
-      link_to content, target, class: klass
-    else
-      content_tag :span, content, class: klass
-    end
-  end
-
-  def ci_text_for_status(status)
-    if detailed_status?(status)
-      status.text
-    else
-      status
-    end
-  end
-
+  # Is used by Commit and Merge Request Widget
   def ci_label_for_status(status)
     if detailed_status?(status)
       return status.label
diff --git a/app/helpers/environment_helper.rb b/app/helpers/environment_helper.rb
index 27975b7ddb73b3bb89ea17abd641222a3dd9a7a7..ff8550439d0e4bd14882f678067e8e715eab8310 100644
--- a/app/helpers/environment_helper.rb
+++ b/app/helpers/environment_helper.rb
@@ -14,10 +14,12 @@ module EnvironmentHelper
     end
   end
 
-  def deployment_link(deployment)
+  def deployment_link(deployment, text: nil)
     return unless deployment
 
-    link_to "##{deployment.iid}", [deployment.project.namespace.becomes(Namespace), deployment.project, deployment.deployable]
+    link_label = text ? text : "##{deployment.iid}"
+
+    link_to link_label, [deployment.project.namespace.becomes(Namespace), deployment.project, deployment.deployable]
   end
 
   def last_deployment_link_for_environment_build(project, build)
diff --git a/app/helpers/gitlab_routing_helper.rb b/app/helpers/gitlab_routing_helper.rb
index af9087d8326aa4b9b580a0f05e30cae8c6eaac7e..99db73c9ee04b2778e922140cf3fbb9266afc47b 100644
--- a/app/helpers/gitlab_routing_helper.rb
+++ b/app/helpers/gitlab_routing_helper.rb
@@ -159,6 +159,11 @@ module GitlabRoutingHelper
     resend_invite_namespace_project_project_member_path(project_member.source.namespace, project_member.source, project_member)
   end
 
+  # Snippets
+  def personal_snippet_url(snippet, *args)
+    snippet_url(snippet)
+  end
+
   # Groups
 
   ## Members
diff --git a/app/helpers/snippets_helper.rb b/app/helpers/snippets_helper.rb
index 7e33a5620775641a918eb28c8a3135b5dff82552..8c02b4061ca0476e8f0e7521ee0bc30badeb222e 100644
--- a/app/helpers/snippets_helper.rb
+++ b/app/helpers/snippets_helper.rb
@@ -8,6 +8,17 @@ module SnippetsHelper
     end
   end
 
+  # Return the path of a snippets index for a user or for a project
+  #
+  # @returns String, path to snippet index
+  def subject_snippets_path(subject = nil, opts = nil)
+    if subject.is_a?(Project)
+      namespace_project_snippets_path(subject.namespace, subject, opts)
+    else # assume subject === User
+      dashboard_snippets_path(opts)
+    end
+  end
+
   # Get an array of line numbers surrounding a matching
   # line, bounded by min/max.
   #
diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb
index 88c46076df612c996114946927f4aeed6bf42d2e..e7cf606a7aed36e767ebd03f38751d2377023a6f 100644
--- a/app/models/ci/build.rb
+++ b/app/models/ci/build.rb
@@ -100,6 +100,12 @@ module Ci
       end
     end
 
+    def detailed_status(current_user)
+      Gitlab::Ci::Status::Build::Factory
+        .new(self, current_user)
+        .fabricate!
+    end
+
     def manual?
       self.when == 'manual'
     end
@@ -123,8 +129,13 @@ module Ci
       end
     end
 
+    def cancelable?
+      active?
+    end
+
     def retryable?
-      project.builds_enabled? && commands.present? && complete?
+      project.builds_enabled? && commands.present? &&
+        (success? || failed? || canceled?)
     end
 
     def retried?
@@ -148,7 +159,7 @@ module Ci
     end
 
     def environment_action
-      self.options.fetch(:environment, {}).fetch(:action, 'start')
+      self.options.fetch(:environment, {}).fetch(:action, 'start') if self.options
     end
 
     def outdated_deployment?
diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb
index fda8228a1e988dc778fbc445353401281742b8c3..54f73171fd467a027e9b93b17c20f15f5c80f706 100644
--- a/app/models/ci/pipeline.rb
+++ b/app/models/ci/pipeline.rb
@@ -336,8 +336,10 @@ module Ci
         .select { |merge_request| merge_request.head_pipeline.try(:id) == self.id }
     end
 
-    def detailed_status
-      Gitlab::Ci::Status::Pipeline::Factory.new(self).fabricate!
+    def detailed_status(current_user)
+      Gitlab::Ci::Status::Pipeline::Factory
+        .new(self, current_user)
+        .fabricate!
     end
 
     private
diff --git a/app/models/ci/stage.rb b/app/models/ci/stage.rb
index d2a37c0a827a49f3a7ff6a5c08d52496c9a452c1..7ef59445d777354bd6e78b1969dc74f64b1d5849 100644
--- a/app/models/ci/stage.rb
+++ b/app/models/ci/stage.rb
@@ -22,8 +22,10 @@ module Ci
       @status ||= statuses.latest.status
     end
 
-    def detailed_status
-      Gitlab::Ci::Status::Stage::Factory.new(self).fabricate!
+    def detailed_status(current_user)
+      Gitlab::Ci::Status::Stage::Factory
+        .new(self, current_user)
+        .fabricate!
     end
 
     def statuses
diff --git a/app/models/commit_status.rb b/app/models/commit_status.rb
index cf90475f4d4502cd173cdec37156a1cf1aff7643..31cd381dcd2d0b16934d5cf717ed5bc6e4cc2cf1 100644
--- a/app/models/commit_status.rb
+++ b/app/models/commit_status.rb
@@ -131,4 +131,10 @@ class CommitStatus < ActiveRecord::Base
   def has_trace?
     false
   end
+
+  def detailed_status(current_user)
+    Gitlab::Ci::Status::Factory
+      .new(self, current_user)
+      .fabricate!
+  end
 end
diff --git a/app/models/concerns/routable.rb b/app/models/concerns/routable.rb
index d36bb9da29683ff497b9fac69e4b219fc6b625d2..1108a64c59e7e06e2c9984939a4c290941e78d15 100644
--- a/app/models/concerns/routable.rb
+++ b/app/models/concerns/routable.rb
@@ -7,6 +7,7 @@ module Routable
     has_one :route, as: :source, autosave: true, dependent: :destroy
 
     validates_associated :route
+    validates :route, presence: true
 
     before_validation :update_route_path, if: :full_path_changed?
   end
@@ -28,17 +29,17 @@ module Routable
 
       order_sql = "(CASE WHEN #{binary} routes.path = #{connection.quote(path)} THEN 0 ELSE 1 END)"
 
-      where_paths_in([path]).reorder(order_sql).take
+      where_full_path_in([path]).reorder(order_sql).take
     end
 
     # Builds a relation to find multiple objects by their full paths.
     #
     # Usage:
     #
-    #     Klass.where_paths_in(%w{gitlab-org/gitlab-ce gitlab-org/gitlab-ee})
+    #     Klass.where_full_path_in(%w{gitlab-org/gitlab-ce gitlab-org/gitlab-ee})
     #
     # Returns an ActiveRecord::Relation.
-    def where_paths_in(paths)
+    def where_full_path_in(paths)
       wheres = []
       cast_lower = Gitlab::Database.postgresql?
 
diff --git a/app/models/project_services/buildkite_service.rb b/app/models/project_services/buildkite_service.rb
index 86a06321e212d48df41b5f48cd011d218f9ff9ff..fe6d7aabb22e45b5ff552e199289b9e367d46c40 100644
--- a/app/models/project_services/buildkite_service.rb
+++ b/app/models/project_services/buildkite_service.rb
@@ -3,7 +3,8 @@ require "addressable/uri"
 class BuildkiteService < CiService
   ENDPOINT = "https://buildkite.com"
 
-  prop_accessor :project_url, :token, :enable_ssl_verification
+  prop_accessor :project_url, :token
+  boolean_accessor :enable_ssl_verification
 
   validates :project_url, presence: true, url: true, if: :activated?
   validates :token, presence: true, if: :activated?
diff --git a/app/models/project_services/drone_ci_service.rb b/app/models/project_services/drone_ci_service.rb
index 5e4dd101c53e6a9713f50168cc4a8fcc10224160..adc78a427ee30e82b446a9c44ab1854127cb561b 100644
--- a/app/models/project_services/drone_ci_service.rb
+++ b/app/models/project_services/drone_ci_service.rb
@@ -1,5 +1,6 @@
 class DroneCiService < CiService
-  prop_accessor :drone_url, :token, :enable_ssl_verification
+  prop_accessor :drone_url, :token
+  boolean_accessor :enable_ssl_verification
 
   validates :drone_url, presence: true, url: true, if: :activated?
   validates :token, presence: true, if: :activated?
diff --git a/app/models/project_services/emails_on_push_service.rb b/app/models/project_services/emails_on_push_service.rb
index e0083c43adb260b7bb0765693d9af7aeacb9520a..79285cbd26de1182ea5cb23d25becc3ccdd623dc 100644
--- a/app/models/project_services/emails_on_push_service.rb
+++ b/app/models/project_services/emails_on_push_service.rb
@@ -1,6 +1,6 @@
 class EmailsOnPushService < Service
-  prop_accessor :send_from_committer_email
-  prop_accessor :disable_diffs
+  boolean_accessor :send_from_committer_email
+  boolean_accessor :disable_diffs
   prop_accessor :recipients
   validates :recipients, presence: true, if: :activated?
 
@@ -24,20 +24,20 @@ class EmailsOnPushService < Service
     return unless supported_events.include?(push_data[:object_kind])
 
     EmailsOnPushWorker.perform_async(
-      project_id, 
-      recipients, 
-      push_data, 
-      send_from_committer_email:  send_from_committer_email?, 
-      disable_diffs:              disable_diffs?
+      project_id,
+      recipients,
+      push_data,
+      send_from_committer_email: send_from_committer_email?,
+      disable_diffs:             disable_diffs?
     )
   end
 
   def send_from_committer_email?
-    self.send_from_committer_email == "1"
+    Gitlab::Utils.to_boolean(self.send_from_committer_email)
   end
 
   def disable_diffs?
-    self.disable_diffs == "1"
+    Gitlab::Utils.to_boolean(self.disable_diffs)
   end
 
   def fields
diff --git a/app/models/project_services/hipchat_service.rb b/app/models/project_services/hipchat_service.rb
index 660a8ae3421ec13b447b270fea5f0184554bf964..915f6fed74c01cf357c81512a7f0341ee63b7bcc 100644
--- a/app/models/project_services/hipchat_service.rb
+++ b/app/models/project_services/hipchat_service.rb
@@ -8,8 +8,8 @@ class HipchatService < Service
     ul ol li dl dt dd
   ]
 
-  prop_accessor :token, :room, :server, :notify, :color, :api_version
-  boolean_accessor :notify_only_broken_builds
+  prop_accessor :token, :room, :server, :color, :api_version
+  boolean_accessor :notify_only_broken_builds, :notify
   validates :token, presence: true, if: :activated?
 
   def initialize_properties
@@ -75,7 +75,7 @@ class HipchatService < Service
   end
 
   def message_options(data = nil)
-    { notify: notify.present? && notify == '1', color: message_color(data) }
+    { notify: notify.present? && Gitlab::Utils.to_boolean(notify), color: message_color(data) }
   end
 
   def create_message(data)
diff --git a/app/models/project_services/irker_service.rb b/app/models/project_services/irker_service.rb
index ce7d1c5d5b136cec34aa69d8004e0dcfdbf102fe..7355918feab407b6c52ad74f54d97e0e1e9b8570 100644
--- a/app/models/project_services/irker_service.rb
+++ b/app/models/project_services/irker_service.rb
@@ -2,7 +2,8 @@ require 'uri'
 
 class IrkerService < Service
   prop_accessor :server_host, :server_port, :default_irc_uri
-  prop_accessor :colorize_messages, :recipients, :channels
+  prop_accessor :recipients, :channels
+  boolean_accessor :colorize_messages
   validates :recipients, presence: true, if: :activated?
 
   before_validation :get_channels
diff --git a/app/models/project_services/jira_service.rb b/app/models/project_services/jira_service.rb
index 894315a8593fa9cf6e817c3abbb270e1f75caf79..2d969d2fcb66bf7539df1bab914cf349b5f7af67 100644
--- a/app/models/project_services/jira_service.rb
+++ b/app/models/project_services/jira_service.rb
@@ -220,7 +220,7 @@ class JiraService < IssueTrackerService
     entity_title = data[:entity][:title]
     project_name = data[:project][:name]
 
-    message      = "[#{user_name}|#{user_url}] mentioned this issue in [a #{entity_name} of #{project_name}|#{entity_url}]:\n'#{entity_title}'"
+    message      = "[#{user_name}|#{user_url}] mentioned this issue in [a #{entity_name} of #{project_name}|#{entity_url}]:\n'#{entity_title.chomp}'"
     link_title   = "GitLab: Mentioned on #{entity_name} - #{entity_title}"
     link_props   = build_remote_link_props(url: entity_url, title: link_title)
 
diff --git a/app/models/user.rb b/app/models/user.rb
index b9bb4a9e3f7e98ec24e216f28bb8c0463cb45f5c..1bd28203523f460de46f88d6a04d8922b6b773c1 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -304,10 +304,6 @@ class User < ActiveRecord::Base
       personal_access_token.user if personal_access_token
     end
 
-    def by_username_or_id(name_or_id)
-      find_by('users.username = ? OR users.id = ?', name_or_id.to_s, name_or_id.to_i)
-    end
-
     # Returns a user for the given SSH key.
     def find_by_ssh_key_id(key_id)
       find_by(id: Key.unscoped.select(:user_id).where(id: key_id))
diff --git a/app/policies/group_member_policy.rb b/app/policies/group_member_policy.rb
index 62335527654cd230a2947a5f52ff97807f0f02fc..5a3fe814b77950a685e3b06f10b3a1966fc53e07 100644
--- a/app/policies/group_member_policy.rb
+++ b/app/policies/group_member_policy.rb
@@ -15,5 +15,11 @@ class GroupMemberPolicy < BasePolicy
     elsif @user == target_user
       can! :destroy_group_member
     end
+
+    additional_rules!
+  end
+
+  def additional_rules!
+    # This is meant to be overriden in EE
   end
 end
diff --git a/app/policies/group_policy.rb b/app/policies/group_policy.rb
index b65fb68cd88b93ce6e5e293fe91595f6c3d88ade..6f943feb2a72690cc51806eb5b98a2b3394cb8eb 100644
--- a/app/policies/group_policy.rb
+++ b/app/policies/group_policy.rb
@@ -33,6 +33,8 @@ class GroupPolicy < BasePolicy
     if globally_viewable && @subject.request_access_enabled && !member
       can! :request_access
     end
+
+    additional_rules!(master)
   end
 
   def can_read_group?
@@ -43,4 +45,8 @@ class GroupPolicy < BasePolicy
 
     GroupProjectsFinder.new(@subject).execute(@user).any?
   end
+
+  def additional_rules!(master)
+    # This is meant to be overriden in EE
+  end
 end
diff --git a/app/policies/personal_snippet_policy.rb b/app/policies/personal_snippet_policy.rb
index 46c5aa1a5be4849b8498d856873707af84b6b8b7..d3913986cd81484b6298cb0c9da9cc78c27a0f37 100644
--- a/app/policies/personal_snippet_policy.rb
+++ b/app/policies/personal_snippet_policy.rb
@@ -6,9 +6,14 @@ class PersonalSnippetPolicy < BasePolicy
     if @subject.author == @user
       can! :read_personal_snippet
       can! :update_personal_snippet
+      can! :destroy_personal_snippet
       can! :admin_personal_snippet
     end
 
+    unless @user.external?
+      can! :create_personal_snippet
+    end
+
     if @subject.internal? && !@user.external?
       can! :read_personal_snippet
     end
diff --git a/app/services/commits/change_service.rb b/app/services/commits/change_service.rb
index db5f2bf9b2e5dd5b73e757522a43404a362bd015..4d410f66c55c7227aa767da5ac509363a39d01fe 100644
--- a/app/services/commits/change_service.rb
+++ b/app/services/commits/change_service.rb
@@ -35,7 +35,7 @@ module Commits
         success
       else
         error_msg = "Sorry, we cannot #{action.to_s.dasherize} this #{@commit.change_type_title(current_user)} automatically.
-                     It may have already been #{action.to_s.dasherize}, or a more recent commit may have updated some of its content."
+                     A #{action.to_s.dasherize} may have already been performed with this #{@commit.change_type_title(current_user)}, or a more recent commit may have updated some of its content."
         raise ChangeError, error_msg
       end
     end
diff --git a/app/views/admin/dashboard/_head.html.haml b/app/views/admin/dashboard/_head.html.haml
index ec40391a3e3a83d2afce1940fdbe65e0672bd840..b5f96363230b5729ffcbd40b04321115491a9523 100644
--- a/app/views/admin/dashboard/_head.html.haml
+++ b/app/views/admin/dashboard/_head.html.haml
@@ -8,7 +8,7 @@
             %span
               Overview
         = nav_link(controller: [:admin, :projects]) do
-          = link_to admin_namespaces_projects_path, title: 'Projects' do
+          = link_to admin_projects_path, title: 'Projects' do
             %span
               Projects
         = nav_link(controller: :users) do
diff --git a/app/views/admin/dashboard/index.html.haml b/app/views/admin/dashboard/index.html.haml
index e51f4ac1d934ac7647db796a92d861298f1eb0ab..5238623e936efdc64db0f7fdc89a11ae028035a4 100644
--- a/app/views/admin/dashboard/index.html.haml
+++ b/app/views/admin/dashboard/index.html.haml
@@ -116,7 +116,7 @@
         .light-well.well-centered
           %h4 Projects
           .data
-            = link_to admin_namespaces_projects_path do
+            = link_to admin_projects_path do
               %h1= number_with_delimiter(Project.cached_count)
             %hr
             = link_to('New Project', new_project_path, class: "btn btn-new")
diff --git a/app/views/admin/groups/_group.html.haml b/app/views/admin/groups/_group.html.haml
index 664bb417c6a70ea4e76829fc7bc106d2b7193171..4efeec0ea4e06c24b89640f2e60e04d1228aa0a7 100644
--- a/app/views/admin/groups/_group.html.haml
+++ b/app/views/admin/groups/_group.html.haml
@@ -2,7 +2,7 @@
 
 %li.group-row{ class: css_class }
   .controls
-    = link_to 'Edit', edit_admin_group_path(group), id: "edit_#{dom_id(group)}", class: 'btn'
+    = link_to 'Edit', admin_group_edit_path(group), id: "edit_#{dom_id(group)}", class: 'btn'
     = link_to 'Delete', [:admin, group], data: { confirm: "Are you sure you want to remove #{group.name}?" }, method: :delete, class: 'btn btn-remove'
   .stats
     %span
diff --git a/app/views/admin/groups/show.html.haml b/app/views/admin/groups/show.html.haml
index 40871e32913bc5b6aae16147e98339aa6f8273e4..71a605f33b1e319dce19bee6c3d50f375f66cf16 100644
--- a/app/views/admin/groups/show.html.haml
+++ b/app/views/admin/groups/show.html.haml
@@ -2,7 +2,7 @@
 %h3.page-title
   Group: #{@group.name}
 
-  = link_to edit_admin_group_path(@group), class: "btn pull-right" do
+  = link_to admin_group_edit_path(@group), class: "btn pull-right" do
     %i.fa.fa-pencil-square-o
     Edit
 %hr
@@ -88,7 +88,7 @@
             Read more about project permissions
             %strong= link_to "here", help_page_path("user/permissions"), class: "vlink"
 
-          = form_tag members_update_admin_group_path(@group), id: "new_project_member", class: "bulk_import", method: :put  do
+          = form_tag admin_group_members_update_path(@group), id: "new_project_member", class: "bulk_import", method: :put  do
             %div
               = users_select_tag(:user_ids, multiple: true, email_user: true, scope: :all)
             %div.prepend-top-10
diff --git a/app/views/admin/projects/index.html.haml b/app/views/admin/projects/index.html.haml
index b37b8d4fee78728d820a7fb0cfc144cbb2016556..8bc7dc7dd51e6805f910758f9574e16c5e6fe525 100644
--- a/app/views/admin/projects/index.html.haml
+++ b/app/views/admin/projects/index.html.haml
@@ -7,7 +7,7 @@
 %div{ class: container_class }
   .top-area
     .prepend-top-default
-      = form_tag admin_namespaces_projects_path, method: :get do |f|
+      = form_tag admin_projects_path, method: :get do |f|
         .search-holder
           .search-field-holder
             = search_field_tag :name, params[:name], class: "form-control search-text-input js-search-input", id: "dashboard_search", autofocus: true, spellcheck: false, placeholder: 'Search by name'
@@ -41,19 +41,19 @@
           = button_tag "Search", class: "btn btn-primary btn-search"
 
     %ul.nav-links
-      - opts = params[:visibility_level].present? ? {} : { page: admin_namespaces_projects_path }
+      - opts = params[:visibility_level].present? ? {} : { page: admin_projects_path }
       = nav_link(opts) do
-        = link_to admin_namespaces_projects_path do
+        = link_to admin_projects_path do
           All
 
       = nav_link(html_options: { class: params[:visibility_level] == Gitlab::VisibilityLevel::PRIVATE.to_s ? 'active' : '' }) do
-        = link_to admin_namespaces_projects_path(visibility_level: Gitlab::VisibilityLevel::PRIVATE) do
+        = link_to admin_projects_path(visibility_level: Gitlab::VisibilityLevel::PRIVATE) do
           Private
       = nav_link(html_options: { class: params[:visibility_level] == Gitlab::VisibilityLevel::INTERNAL.to_s ? 'active' : '' }) do
-        = link_to admin_namespaces_projects_path(visibility_level: Gitlab::VisibilityLevel::INTERNAL) do
+        = link_to admin_projects_path(visibility_level: Gitlab::VisibilityLevel::INTERNAL) do
           Internal
       = nav_link(html_options: { class: params[:visibility_level] == Gitlab::VisibilityLevel::PUBLIC.to_s ? 'active' : '' }) do
-        = link_to admin_namespaces_projects_path(visibility_level: Gitlab::VisibilityLevel::PUBLIC) do
+        = link_to admin_projects_path(visibility_level: Gitlab::VisibilityLevel::PUBLIC) do
           Public
 
     .nav-controls
diff --git a/app/views/admin/runners/show.html.haml b/app/views/admin/runners/show.html.haml
index 73038164056f7be693c666305358da2b631f3f8b..ca503e35623a5f24650160d3c31300cc9854159d 100644
--- a/app/views/admin/runners/show.html.haml
+++ b/app/views/admin/runners/show.html.haml
@@ -91,7 +91,7 @@
               %strong ##{build.id}
 
           %td.status
-            = ci_status_with_icon(build.status)
+            = render 'ci/status/badge', status: build.detailed_status(current_user)
 
           %td.status
             - if project
diff --git a/app/views/ci/status/_badge.html.haml b/app/views/ci/status/_badge.html.haml
new file mode 100644
index 0000000000000000000000000000000000000000..f2135af268679d52629575635a9e91bb306ac5d4
--- /dev/null
+++ b/app/views/ci/status/_badge.html.haml
@@ -0,0 +1,10 @@
+- status = local_assigns.fetch(:status)
+
+- if status.has_details?
+  = link_to status.details_path, class: "ci-status ci-#{status}" do
+    = custom_icon(status.icon)
+    = status.text
+- else
+  %span{ class: "ci-status ci-#{status}" }
+    = custom_icon(status.icon)
+    = status.text
diff --git a/app/views/dashboard/_snippets_head.html.haml b/app/views/dashboard/_snippets_head.html.haml
index b25e8ea1f0cdfb30cef06192024684e9508589a4..02e90bbfa55336e642145df7850e0eb7fd639c72 100644
--- a/app/views/dashboard/_snippets_head.html.haml
+++ b/app/views/dashboard/_snippets_head.html.haml
@@ -1,7 +1,13 @@
-%ul.nav-links
-  = nav_link(page: dashboard_snippets_path, html_options: {class: 'home'}) do
-    = link_to dashboard_snippets_path, title: 'Your snippets', data: {placement: 'right'} do
-      Your Snippets
-  = nav_link(page: explore_snippets_path) do
-    = link_to explore_snippets_path, title: 'Explore snippets', data: {placement: 'right'} do
-      Explore Snippets
+.top-area
+  %ul.nav-links
+    = nav_link(page: dashboard_snippets_path, html_options: {class: 'home'}) do
+      = link_to dashboard_snippets_path, title: 'Your snippets', data: {placement: 'right'} do
+        Your Snippets
+    = nav_link(page: explore_snippets_path) do
+      = link_to explore_snippets_path, title: 'Explore snippets', data: {placement: 'right'} do
+        Explore Snippets
+
+  - if current_user
+    .nav-controls.hidden-xs
+      = link_to new_snippet_path, class: "btn btn-new", title: "New snippet" do
+        New snippet
diff --git a/app/views/dashboard/snippets/index.html.haml b/app/views/dashboard/snippets/index.html.haml
index b2af438ea576706cc167915984753089f15d87fb..85cbe0bf0e6e7a6e754c8a0b9413c6be95c1ab7b 100644
--- a/app/views/dashboard/snippets/index.html.haml
+++ b/app/views/dashboard/snippets/index.html.haml
@@ -2,41 +2,11 @@
 - header_title  "Snippets", dashboard_snippets_path
 
 = render 'dashboard/snippets_head'
+= render partial: 'snippets/snippets_scope_menu', locals: { include_private: true }
 
-.nav-block
-  .controls.hidden-xs
-    = link_to new_snippet_path, class: "btn btn-new", title: "New snippet" do
-      = icon('plus')
-      New snippet
+.visible-xs
+  &nbsp;
+  = link_to new_snippet_path, class: "btn btn-new btn-block", title: "New snippet" do
+    New snippet
 
-  .nav-links.snippet-scope-menu
-    %li{ class: ("active" unless params[:scope]) }
-      = link_to dashboard_snippets_path do
-        All
-        %span.badge
-          = current_user.snippets.count
-
-    %li{ class: ("active" if params[:scope] == "are_private") }
-      = link_to dashboard_snippets_path(scope: 'are_private') do
-        Private
-        %span.badge
-          = current_user.snippets.are_private.count
-
-    %li{ class: ("active" if params[:scope] == "are_internal") }
-      = link_to dashboard_snippets_path(scope: 'are_internal') do
-        Internal
-        %span.badge
-          = current_user.snippets.are_internal.count
-
-    %li{ class: ("active" if params[:scope] == "are_public") }
-      = link_to dashboard_snippets_path(scope: 'are_public') do
-        Public
-        %span.badge
-          = current_user.snippets.are_public.count
-
-    .visible-xs
-      = link_to new_snippet_path, class: "btn btn-new btn-block", title: "New snippet" do
-        = icon('plus')
-        New snippet
-
-= render 'snippets/snippets'
+= render partial: 'snippets/snippets', locals: { link_project: true }
diff --git a/app/views/explore/snippets/index.html.haml b/app/views/explore/snippets/index.html.haml
index 7def9eacdc9f3e11d57c26aad69b254e22150dc5..e5706d0473628731828d63d942443b6a12d64cc0 100644
--- a/app/views/explore/snippets/index.html.haml
+++ b/app/views/explore/snippets/index.html.haml
@@ -6,12 +6,4 @@
 - else
   = render 'explore/head'
 
-.row-content-block
-  - if current_user
-    = link_to new_snippet_path, class: "btn btn-new btn-wide-on-sm pull-right", title: "New snippet" do
-      New snippet
-
-  .oneline
-    Public snippets created by you and other users are listed here
-
-= render 'snippets/snippets'
+= render partial: 'snippets/snippets', locals: { link_project: true }
diff --git a/app/views/groups/issues.html.haml b/app/views/groups/issues.html.haml
index 324a116a50e5c4a9fbc3d08b4bb466d8f48ad7a4..b4aa4f24d9e5f03e552e1fe6864f2b216111451e 100644
--- a/app/views/groups/issues.html.haml
+++ b/app/views/groups/issues.html.haml
@@ -6,13 +6,13 @@
 - if group_issues(@group).exists?
   .top-area
     = render 'shared/issuable/nav', type: :issues
-    .nav-controls
-      - if current_user
+    - if current_user
+      .nav-controls
         = link_to url_for(params.merge(format: :atom, private_token: current_user.private_token)), class: 'btn' do
           = icon('rss')
           %span.icon-label
             Subscribe
-      = render 'shared/new_project_item_select', path: 'issues/new', label: "New Issue"
+        = render 'shared/new_project_item_select', path: 'issues/new', label: "New Issue"
 
   = render 'shared/issuable/filter', type: :issues
 
diff --git a/app/views/groups/merge_requests.html.haml b/app/views/groups/merge_requests.html.haml
index e6953d94531d383598ddacc6a28ef02d12a9cada..dbbdb583a24d94e50d2e20e988205653b16f1401 100644
--- a/app/views/groups/merge_requests.html.haml
+++ b/app/views/groups/merge_requests.html.haml
@@ -2,8 +2,9 @@
 
 .top-area
   = render 'shared/issuable/nav', type: :merge_requests
-  .nav-controls
-    = render 'shared/new_project_item_select', path: 'merge_requests/new', label: "New Merge Request"
+  - if current_user
+    .nav-controls
+      = render 'shared/new_project_item_select', path: 'merge_requests/new', label: "New Merge Request"
 
 = render 'shared/issuable/filter', type: :merge_requests
 
diff --git a/app/views/profiles/notifications/show.html.haml b/app/views/profiles/notifications/show.html.haml
index 844fce59704582e4a3382de5c99d4b167d7de154..d79a1a9f3682db4462eccbcb826da607165b96c0 100644
--- a/app/views/profiles/notifications/show.html.haml
+++ b/app/views/profiles/notifications/show.html.haml
@@ -30,7 +30,7 @@
       %br
       .clearfix
       .form-group.pull-left.global-notification-setting
-        = render 'shared/notifications/button', notification_setting: @global_notification_setting, left_align: true
+        = render 'shared/notifications/button', notification_setting: @global_notification_setting
 
       .clearfix
 
diff --git a/app/views/projects/builds/_header.html.haml b/app/views/projects/builds/_header.html.haml
index f6aa20c4579cb154b87ac4573d8d3ade684eff21..057a720a54a7cef7bde405682945da0a7b60cc1d 100644
--- a/app/views/projects/builds/_header.html.haml
+++ b/app/views/projects/builds/_header.html.haml
@@ -1,6 +1,6 @@
 .content-block.build-header
   .header-content
-    = ci_status_with_icon(@build.status)
+    = render 'ci/status/badge', status: @build.detailed_status(current_user)
     Build
     %strong ##{@build.id}
     in pipeline
diff --git a/app/views/projects/builds/show.html.haml b/app/views/projects/builds/show.html.haml
index 108674dbba66bb872dca41fbc8c9164086b2e828..cdeb81372ee1b1684fc3767f9ab51c0877638810 100644
--- a/app/views/projects/builds/show.html.haml
+++ b/app/views/projects/builds/show.html.haml
@@ -46,8 +46,7 @@
           - else
             This build is creating a deployment to #{environment_link_for_build(@build.project, @build)}
             - if environment.try(:last_deployment)
-              and will overwrite the
-              = link_to 'latest deployment', deployment_link(environment.last_deployment)
+              and will overwrite the #{deployment_link(environment.last_deployment, text: 'latest deployment')}
 
     .prepend-top-default
       - if @build.erased?
diff --git a/app/views/projects/ci/builds/_build.html.haml b/app/views/projects/ci/builds/_build.html.haml
index 18b3b04154f4936ec7c99350572a775fb19d4a6d..f1cb020103224557f38af0a57a4f69904a8295b2 100644
--- a/app/views/projects/ci/builds/_build.html.haml
+++ b/app/views/projects/ci/builds/_build.html.haml
@@ -9,10 +9,7 @@
 
 %tr.build.commit{class: ('retried' if retried)}
   %td.status
-    - if can?(current_user, :read_build, build)
-      = ci_status_with_icon(build.status, namespace_project_build_url(build.project.namespace, build.project, build))
-    - else
-      = ci_status_with_icon(build.status)
+    = render "ci/status/badge", status: build.detailed_status(current_user)
 
   %td.branch-commit
     - if can?(current_user, :read_build, build)
diff --git a/app/views/projects/ci/builds/_build_pipeline.html.haml b/app/views/projects/ci/builds/_build_pipeline.html.haml
index 423a1282eb2f6129b2b257b96e838cfb5231f84d..ad1a7360a8b3e827318de965eeee0bb337a81431 100644
--- a/app/views/projects/ci/builds/_build_pipeline.html.haml
+++ b/app/views/projects/ci/builds/_build_pipeline.html.haml
@@ -1,10 +1,10 @@
 - is_playable = subject.playable? && can?(current_user, :update_build, @project)
 - if is_playable
-  = link_to play_namespace_project_build_path(subject.project.namespace, subject.project, subject, return_to: request.original_url), method: :post, data: { toggle: 'tooltip', title: "#{subject.name} - play", container: '.pipeline-graph', placement: 'bottom' } do
+  = link_to play_namespace_project_build_path(subject.project.namespace, subject.project, subject, return_to: request.original_url), method: :post, data: { toggle: 'tooltip', title: "#{subject.name} - play", container: '.js-pipeline-graph', placement: 'bottom' } do
     = ci_icon_for_status('play')
     .ci-status-text= subject.name
 - elsif can?(current_user, :read_build, @project)
-  = link_to namespace_project_build_path(subject.project.namespace, subject.project, subject), data: { toggle: 'tooltip', title: "#{subject.name} - #{subject.status}", container: '.pipeline-graph', placement: 'bottom' } do
+  = link_to namespace_project_build_path(subject.project.namespace, subject.project, subject), data: { toggle: 'tooltip', title: "#{subject.name} - #{subject.status}", container: '.js-pipeline-graph', placement: 'bottom' } do
     %span{class: "ci-status-icon ci-status-icon-#{subject.status}"}
       = ci_icon_for_status(subject.status)
     .ci-status-text= subject.name
diff --git a/app/views/projects/ci/pipelines/_pipeline.html.haml b/app/views/projects/ci/pipelines/_pipeline.html.haml
index b58dceb58c9cdb3edae8c67599bb96144fe66d14..3f05a21990f20efd11a6953cd39a454201d91436 100644
--- a/app/views/projects/ci/pipelines/_pipeline.html.haml
+++ b/app/views/projects/ci/pipelines/_pipeline.html.haml
@@ -1,13 +1,10 @@
 - status = pipeline.status
-- detailed_status = pipeline.detailed_status
 - show_commit = local_assigns.fetch(:show_commit, true)
 - show_branch = local_assigns.fetch(:show_branch, true)
 
 %tr.commit
   %td.commit-link
-    = link_to namespace_project_pipeline_path(pipeline.project.namespace, pipeline.project, pipeline.id), class: "ci-status ci-#{detailed_status}" do
-      = ci_icon_for_status(detailed_status)
-      = ci_text_for_status(detailed_status)
+    = render 'ci/status/badge', status: pipeline.detailed_status(current_user)
 
   %td
     = link_to namespace_project_pipeline_path(pipeline.project.namespace, pipeline.project, pipeline.id) do
diff --git a/app/views/projects/commit/_change.html.haml b/app/views/projects/commit/_change.html.haml
index f6e3d5e76f5df4b22af3c4eacbffcb3ce7aa4826..782f558e8b081c59fd6b43787d6c63f54a92d026 100644
--- a/app/views/projects/commit/_change.html.haml
+++ b/app/views/projects/commit/_change.html.haml
@@ -13,7 +13,7 @@
         %a.close{href: "#", "data-dismiss" => "modal"} ×
         %h3.page-title== #{label} this #{commit.change_type_title(current_user)}
       .modal-body
-        = form_tag send("#{type.underscore}_namespace_project_commit_path", @project.namespace, @project, commit.id), method: :post, remote: false, class: 'form-horizontal js-#{type}-form js-requires-input' do
+        = form_tag send("#{type.underscore}_namespace_project_commit_path", @project.namespace, @project, commit.id), method: :post, remote: false, class: "form-horizontal js-#{type}-form js-requires-input" do
           .form-group.branch
             = label_tag 'target_branch', target_label, class: 'control-label'
             .col-sm-10
@@ -23,12 +23,11 @@
               - if can?(current_user, :push_code, @project)
                 .js-create-merge-request-container
                   .checkbox
-                    - nonce = SecureRandom.hex
-                    = label_tag "create_merge_request-#{nonce}" do
-                      = check_box_tag 'create_merge_request', 1, true, class: 'js-create-merge-request', id: "create_merge_request-#{nonce}"
+                    = label_tag do
+                      = check_box_tag 'create_merge_request', 1, true, class: 'js-create-merge-request', id: nil
                       Start a <strong>new merge request</strong> with these changes
               - else
-                = hidden_field_tag 'create_merge_request', 1
+                = hidden_field_tag 'create_merge_request', 1, id: nil
           .form-actions
             = submit_tag label, class: 'btn btn-create'
             = link_to "Cancel", '#', class: "btn btn-cancel", "data-dismiss" => "modal"
diff --git a/app/views/projects/commit/_commit_box.html.haml b/app/views/projects/commit/_commit_box.html.haml
index 65151ac3a56c98016c2452b4a9f10b4e2bb6b8d7..c08ed8f6c1687fc2c244ae0f794b3ca6780076ba 100644
--- a/app/views/projects/commit/_commit_box.html.haml
+++ b/app/views/projects/commit/_commit_box.html.haml
@@ -1,13 +1,8 @@
 .page-content-header
   .header-main-content
-    %strong Commit
-    %strong.monospace.js-details-short= @commit.short_id
-    = link_to("#", class: "js-details-expand hidden-xs hidden-sm") do
-      %span.text-expander
-        \...
-    %span.js-details-content.hide
-      %strong.monospace.commit-hash-full= @commit.id
+    %strong
     = clipboard_button(clipboard_text: @commit.id)
+    = @commit.short_id
     %span.hidden-xs authored
     #{time_ago_with_tooltip(@commit.authored_date)}
     %span by
diff --git a/app/views/projects/commit/_pipeline.html.haml b/app/views/projects/commit/_pipeline.html.haml
index c7b5c1124b37a56cbb75e2de5a454f79a472c0b2..08d3443b3d02f8afbf68052b25af604805aa636e 100644
--- a/app/views/projects/commit/_pipeline.html.haml
+++ b/app/views/projects/commit/_pipeline.html.haml
@@ -24,7 +24,7 @@
           in
           = time_interval_in_words pipeline.duration
 
-  .row-content-block.build-content.middle-block.hidden
+  .row-content-block.build-content.middle-block.js-pipeline-graph.hidden
     = render "projects/pipelines/graph", pipeline: pipeline
 
 - if pipeline.yaml_errors.present?
diff --git a/app/views/projects/generic_commit_statuses/_generic_commit_status.html.haml b/app/views/projects/generic_commit_statuses/_generic_commit_status.html.haml
index 7f751d9ae2e87c032cee2abdb2e2d928098b541b..9f444f076c05427cd406725b5a693b9f85269268 100644
--- a/app/views/projects/generic_commit_statuses/_generic_commit_status.html.haml
+++ b/app/views/projects/generic_commit_statuses/_generic_commit_status.html.haml
@@ -8,10 +8,7 @@
 
 %tr.generic_commit_status{class: ('retried' if retried)}
   %td.status
-    - if can?(current_user, :read_commit_status, generic_commit_status) && generic_commit_status.target_url
-      = ci_status_with_icon(generic_commit_status.status, generic_commit_status.target_url)
-    - else
-      = ci_status_with_icon(generic_commit_status.status)
+    = render 'ci/status/badge', status: generic_commit_status.detailed_status(current_user)
 
   %td.generic_commit_status-link
     - if can?(current_user, :read_commit_status, generic_commit_status) && generic_commit_status.target_url
diff --git a/app/views/projects/generic_commit_statuses/_generic_commit_status_pipeline.html.haml b/app/views/projects/generic_commit_statuses/_generic_commit_status_pipeline.html.haml
index 7b82d913d293a306efceeb27987173c849c5a175..1bba04431542e1128b7514e1a9dc970534c32193 100644
--- a/app/views/projects/generic_commit_statuses/_generic_commit_status_pipeline.html.haml
+++ b/app/views/projects/generic_commit_statuses/_generic_commit_status_pipeline.html.haml
@@ -1,4 +1,4 @@
-%a{ data: { toggle: 'tooltip', title: "#{subject.name} - #{subject.status}", container: '.pipeline-graph', placement: 'bottom' } }
+%a{ data: { toggle: 'tooltip', title: "#{subject.name} - #{subject.status}", container: '.js-pipeline-graph', placement: 'bottom' } }
   - if subject.target_url
     = link_to subject.target_url do
       %span{class: "ci-status-icon ci-status-icon-#{subject.status}"}
diff --git a/app/views/projects/notes/_note.html.haml b/app/views/projects/notes/_note.html.haml
index ba8895438c5cacd74cdf89cb4b8d859ab355a344..778a32e63453659ace9b284ead7ffa9542397620 100644
--- a/app/views/projects/notes/_note.html.haml
+++ b/app/views/projects/notes/_note.html.haml
@@ -65,7 +65,7 @@
         .note-text.md
           = preserve do
             = note.redacted_note_html
-          = edited_time_ago_with_tooltip(note, placement: 'bottom', html_class: 'note_edited_ago', include_author: true)
+        = edited_time_ago_with_tooltip(note, placement: 'bottom', html_class: 'note_edited_ago', include_author: true)
         - if note_editable
           = render 'projects/notes/edit_form', note: note
         .note-awards
diff --git a/app/views/projects/pipelines/_info.html.haml b/app/views/projects/pipelines/_info.html.haml
index 229bdfb0e8d983c9f996d92837ec9253cdefac45..b00ba2d5307c60c55e0ea280cdd5d9bb14c33778 100644
--- a/app/views/projects/pipelines/_info.html.haml
+++ b/app/views/projects/pipelines/_info.html.haml
@@ -1,6 +1,6 @@
 .page-content-header
   .header-main-content
-    = ci_status_with_icon(@pipeline.detailed_status)
+    = render 'ci/status/badge', status: @pipeline.detailed_status(current_user)
     %strong Pipeline ##{@commit.pipelines.last.id}
     triggered #{time_ago_with_tooltip(@commit.authored_date)} by
     = author_avatar(@commit, size: 24)
diff --git a/app/views/projects/pipelines/_with_tabs.html.haml b/app/views/projects/pipelines/_with_tabs.html.haml
index 739e59308224cbf46fcbd92fe77c6e6a7a2f1649..88af41aa83516987397dc909fad01b86fced2fd3 100644
--- a/app/views/projects/pipelines/_with_tabs.html.haml
+++ b/app/views/projects/pipelines/_with_tabs.html.haml
@@ -12,7 +12,7 @@
 
 .tab-content
   #js-tab-pipeline.tab-pane
-    .build-content.middle-block
+    .build-content.middle-block.js-pipeline-graph
       = render "projects/pipelines/graph", pipeline: pipeline
 
   #js-tab-builds.tab-pane
diff --git a/app/views/projects/snippets/_actions.html.haml b/app/views/projects/snippets/_actions.html.haml
index 32e1f8a21b004a4de7ca7e35358593a179e57f8b..068a66103501e17fbdb7d8130c9f8960dd20c53e 100644
--- a/app/views/projects/snippets/_actions.html.haml
+++ b/app/views/projects/snippets/_actions.html.haml
@@ -1,13 +1,13 @@
 .hidden-xs
-  - if can?(current_user, :create_project_snippet, @project)
-    = link_to new_namespace_project_snippet_path(@project.namespace, @project), class: 'btn btn-grouped btn-create new-snippet-link', title: "New snippet" do
-      New snippet
-  - if can?(current_user, :update_project_snippet, @snippet)
-    = link_to namespace_project_snippet_path(@project.namespace, @project, @snippet), method: :delete, data: { confirm: "Are you sure?" }, class: "btn btn-grouped btn-danger", title: 'Delete Snippet' do
-      Delete
   - if can?(current_user, :update_project_snippet, @snippet)
-    = link_to edit_namespace_project_snippet_path(@project.namespace, @project, @snippet), class: "btn btn-grouped snippable-edit" do
+    = link_to edit_namespace_project_snippet_path(@project.namespace, @project, @snippet), class: "btn btn-grouped" do
       Edit
+  - if can?(current_user, :update_project_snippet, @snippet)
+    = link_to namespace_project_snippet_path(@project.namespace, @project, @snippet), method: :delete, data: { confirm: "Are you sure?" }, class: "btn btn-grouped btn-inverted btn-remove", title: 'Delete Snippet' do
+      Delete
+  - if can?(current_user, :create_project_snippet, @project)
+    = link_to new_namespace_project_snippet_path(@project.namespace, @project), class: 'btn btn-grouped btn-inverted btn-create', title: "New snippet" do
+      New snippet
 - if can?(current_user, :create_project_snippet, @project) || can?(current_user, :update_project_snippet, @snippet)
   .visible-xs-block.dropdown
     %button.btn.btn-default.btn-block.append-bottom-0.prepend-top-5{ data: { toggle: "dropdown" } }
diff --git a/app/views/projects/snippets/index.html.haml b/app/views/projects/snippets/index.html.haml
index e77e1b026f6ec236b76104d6108def4968c9fa3a..84e05cd6d88b49c1fd34282af7dc47a6b03d09d2 100644
--- a/app/views/projects/snippets/index.html.haml
+++ b/app/views/projects/snippets/index.html.haml
@@ -1,11 +1,19 @@
 - page_title "Snippets"
 
-.sub-header-block
-  - if can?(current_user, :create_project_snippet, @project)
-    = link_to new_namespace_project_snippet_path(@project.namespace, @project), class: "btn btn-new btn-wide-on-sm pull-right", title: "New snippet" do
-      New snippet
+- if current_user
+  .top-area
+    - include_private = @project.team.member?(current_user) || current_user.admin?
+    = render partial: 'snippets/snippets_scope_menu', locals: { subject: @project, include_private: include_private }
+
+    .nav-controls.hidden-xs
+      - if can?(current_user, :create_project_snippet, @project)
+        = link_to new_namespace_project_snippet_path(@project.namespace, @project), class: "btn btn-new", title: "New snippet" do
+          New snippet
 
-  .oneline
-    Share code pastes with others out of git repository
+- if can?(current_user, :create_project_snippet, @project)
+  .visible-xs
+    &nbsp;
+    = link_to new_namespace_project_snippet_path(@project.namespace, @project), class: "btn btn-new btn-block", title: "New snippet" do
+      New snippet
 
 = render 'snippets/snippets'
diff --git a/app/views/projects/stage/_graph.html.haml b/app/views/projects/stage/_graph.html.haml
index 1d8fa10db0c886096c23030a392e054eb6397370..bf8c75b6e5c6e240e2846406224667eeadf91863 100644
--- a/app/views/projects/stage/_graph.html.haml
+++ b/app/views/projects/stage/_graph.html.haml
@@ -19,4 +19,4 @@
           %li.build
             .curve
             .dropdown.inline.build-content
-              = render "projects/stage/in_stage_group", name: group_name, subject: grouped_statuses
+              = render 'projects/stage/in_stage_group', name: group_name, subject: grouped_statuses
diff --git a/app/views/projects/tree/_tree_content.html.haml b/app/views/projects/tree/_tree_content.html.haml
index 21e378b87355a60518d3f5aa96af10977f029eda..d37c376c36b375d265ecbb27e75d4de3153a6a4b 100644
--- a/app/views/projects/tree/_tree_content.html.haml
+++ b/app/views/projects/tree/_tree_content.html.haml
@@ -5,14 +5,11 @@
         %tr
           %th Name
           %th.hidden-xs
-            .pull-left Last Commit
+            .pull-left Last commit
             .last-commit.hidden-sm.pull-left
-              &nbsp;
-              %i.fa.fa-angle-right
-              &nbsp;
-              %small.light
+              %small.light              
+                = clipboard_button(clipboard_text: @commit.id)
                 = link_to @commit.short_id, namespace_project_commit_path(@project.namespace, @project, @commit), class: "monospace"
-                &ndash;
                 = time_ago_with_tooltip(@commit.committed_date)
                 = @commit.full_title
             %small.commit-history-link-spacer &#124;
diff --git a/app/views/repository_check_mailer/notify.html.haml b/app/views/repository_check_mailer/notify.html.haml
index a585147ddd18fd36194b0f52e0f612e460258ee5..94e5a5d9709997465d203cd82553f8c44e60d4ad 100644
--- a/app/views/repository_check_mailer/notify.html.haml
+++ b/app/views/repository_check_mailer/notify.html.haml
@@ -2,7 +2,7 @@
   #{@message}.
 
 %p
-  = link_to "See the affected projects in the GitLab admin panel", admin_namespaces_projects_url(last_repository_check_failed: 1)
+  = link_to "See the affected projects in the GitLab admin panel", admin_projects_url(last_repository_check_failed: 1)
 
 %p
   You are receiving this message because you are a GitLab administrator for #{Gitlab.config.gitlab.url}.
diff --git a/app/views/repository_check_mailer/notify.text.haml b/app/views/repository_check_mailer/notify.text.haml
index 93db151329efecc297ef3be4c8b1700b3ecccbda..0902c50d0527e96944fa2c45da1d3b7965d2bf2e 100644
--- a/app/views/repository_check_mailer/notify.text.haml
+++ b/app/views/repository_check_mailer/notify.text.haml
@@ -1,6 +1,6 @@
 #{@message}.
 \
-View details: #{admin_namespaces_projects_url(last_repository_check_failed: 1)}
+View details: #{admin_projects_url(last_repository_check_failed: 1)}
 
 You are receiving this message because you are a GitLab administrator
 for #{Gitlab.config.gitlab.url}.
diff --git a/app/views/search/results/_snippet_title.html.haml b/app/views/search/results/_snippet_title.html.haml
index c414acb6a11118080b000133a372f6df57984bc9..027d42396b48f42e52223b1870523f459bf80428 100644
--- a/app/views/search/results/_snippet_title.html.haml
+++ b/app/views/search/results/_snippet_title.html.haml
@@ -14,7 +14,7 @@
       = link_to snippet_title.project.name_with_namespace, namespace_project_path(snippet_title.project.namespace, snippet_title.project)
 
   .snippet-info
-    = "##{snippet_title.id}"
+    = snippet_title.to_reference
     %span
       by
       = link_to user_snippets_path(snippet_title.author) do
diff --git a/app/views/shared/icons/_icon_status_manual.svg b/app/views/shared/icons/_icon_status_manual.svg
new file mode 100755
index 0000000000000000000000000000000000000000..c98839f51a933196085aef6dc364858ca8dc0825
--- /dev/null
+++ b/app/views/shared/icons/_icon_status_manual.svg
@@ -0,0 +1 @@
+<svg width="14" height="14" viewBox="0 0 14 14" xmlns="http://www.w3.org/2000/svg"><g fill-rule="evenodd"><path d="M0 7a7 7 0 1 1 14 0A7 7 0 0 1 0 7z"/><path d="M13 7A6 6 0 1 0 1 7a6 6 0 0 0 12 0z" fill="#FFF"/><path d="M10.5 7.63V6.37l-.787-.13c-.044-.175-.132-.349-.263-.61l.481-.652-.918-.913-.657.478a2.346 2.346 0 0 0-.612-.26L7.656 3.5H6.388l-.132.783c-.219.043-.394.13-.612.26l-.657-.478-.918.913.437.652c-.131.218-.175.392-.262.61l-.744.086v1.261l.787.13c.044.218.132.392.263.61l-.438.651.92.913.655-.434c.175.086.394.173.613.26l.131.783h1.313l.131-.783c.219-.043.394-.13.613-.26l.656.478.918-.913-.48-.652c.13-.218.218-.435.262-.61l.656-.13zM7 8.283a1.285 1.285 0 0 1-1.313-1.305c0-.739.57-1.304 1.313-1.304.744 0 1.313.565 1.313 1.304 0 .74-.57 1.305-1.313 1.305z"/></g></svg>
diff --git a/app/views/shared/notifications/_button.html.haml b/app/views/shared/notifications/_button.html.haml
index 1f7df0bcd194a22cb3f265884c2991341464a64a..fbad0d05de308a9402f4df27e9b4150eb13eaa72 100644
--- a/app/views/shared/notifications/_button.html.haml
+++ b/app/views/shared/notifications/_button.html.haml
@@ -1,4 +1,3 @@
-- left_align = local_assigns[:left_align]
 - if notification_setting
   .dropdown.notification-dropdown
     = form_for notification_setting, remote: true, html: { class: "inline notification-form" } do |f|
@@ -19,7 +18,7 @@
               = notification_title(notification_setting.level)
               = icon("caret-down")
 
-          = render "shared/notifications/notification_dropdown", notification_setting: notification_setting, left_align: left_align
+          = render "shared/notifications/notification_dropdown", notification_setting: notification_setting
 
           = content_for :scripts_body do
             = render "shared/notifications/custom_notifications", notification_setting: notification_setting
diff --git a/app/views/shared/notifications/_notification_dropdown.html.haml b/app/views/shared/notifications/_notification_dropdown.html.haml
index d3258ee64cb42dacef7106f882e6b773a86509dc..85ad74f9a390b2ba9de3145f19bd325ae1895310 100644
--- a/app/views/shared/notifications/_notification_dropdown.html.haml
+++ b/app/views/shared/notifications/_notification_dropdown.html.haml
@@ -1,5 +1,4 @@
-- left_align = local_assigns[:left_align]
-%ul.dropdown-menu.dropdown-menu-no-wrap.dropdown-menu-selectable.dropdown-menu-large{ role: "menu", class: [notifications_menu_identifier("dropdown", notification_setting), ("dropdown-menu-align-right" unless left_align)] }
+%ul.dropdown-menu.dropdown-menu-no-wrap.dropdown-menu-selectable.dropdown-menu-large{ role: "menu", class: [notifications_menu_identifier("dropdown", notification_setting)] }
   - NotificationSetting.levels.each_key do |level|
     - next if level == "custom"
     - next if level == "global" && notification_setting.source.nil?
diff --git a/app/views/shared/snippets/_header.html.haml b/app/views/shared/snippets/_header.html.haml
index d7506e07ff6b0663445658d2e14eaab029cc60cc..d084f5e9684167b22cd6b2311b06afe8c1bd1e71 100644
--- a/app/views/shared/snippets/_header.html.haml
+++ b/app/views/shared/snippets/_header.html.haml
@@ -8,10 +8,6 @@
   %span.creator
     authored
     = time_ago_with_tooltip(@snippet.created_at, placement: 'bottom', html_class: 'snippet_updated_ago')
-    - if @snippet.updated_at != @snippet.created_at
-      %span
-        = icon('edit', title: 'edited')
-        = time_ago_with_tooltip(@snippet.updated_at, placement: 'bottom', html_class: 'snippet_edited_ago')
     by #{link_to_member(@project, @snippet.author, size: 24, author_class: "author item-title", avatar_class: "hidden-xs")}
 
   .snippet-actions
@@ -20,5 +16,9 @@
     - else
       = render "snippets/actions"
 
-%h2.snippet-title.prepend-top-0.append-bottom-0
-  = markdown_field(@snippet, :title)
+.snippet-header
+  %h2.snippet-title.prepend-top-0.append-bottom-0
+    = markdown_field(@snippet, :title)
+
+  - if @snippet.updated_at != @snippet.created_at
+    = edited_time_ago_with_tooltip(@snippet, placement: 'bottom', html_class: 'snippet-edited-ago')
diff --git a/app/views/shared/snippets/_snippet.html.haml b/app/views/shared/snippets/_snippet.html.haml
index ea17bec8677ed48680cbb7c8abbdaebfda7396f5..5d2d2317f22697394490fa6db051cf04c3d0630f 100644
--- a/app/views/shared/snippets/_snippet.html.haml
+++ b/app/views/shared/snippets/_snippet.html.haml
@@ -1,17 +1,16 @@
+- link_project = local_assigns.fetch(:link_project, false)
+
 %li.snippet-row
   = image_tag avatar_icon(snippet.author_email), class: "avatar s40 hidden-xs", alt: ''
 
   .title
     = link_to reliable_snippet_path(snippet) do
       = snippet.title
-      - if snippet.private?
-        %span.label.label-gray.hidden-xs
-          = icon('lock')
-          private
-    %span.monospace.pull-right.hidden-xs
-      = snippet.file_name
+    - if snippet.file_name
+      %span.snippet-filename.monospace.hidden-xs
+        = snippet.file_name
 
-    %ul.controls.visible-xs
+    %ul.controls
       %li
         - note_count = snippet.notes.user.count
         = link_to reliable_snippet_path(snippet, anchor: 'notes'), class: ('no-comments' if note_count.zero?) do
@@ -22,11 +21,17 @@
           = visibility_level_label(snippet.visibility_level)
         = visibility_level_icon(snippet.visibility_level, fw: false)
 
-  %small.pull-right.cgray.hidden-xs
-    - if snippet.project_id?
-      = link_to snippet.project.name_with_namespace, namespace_project_path(snippet.project.namespace, snippet.project)
-
-  .snippet-info.hidden-xs
+  .snippet-info
+    #{snippet.to_reference} &middot;
+    authored #{time_ago_with_tooltip(snippet.created_at, placement: 'bottom', html_class: 'snippet-created-ago')}
+    by
     = link_to user_snippets_path(snippet.author) do
       = snippet.author_name
-    authored #{time_ago_with_tooltip(snippet.created_at)}
+    - if link_project && snippet.project_id?
+      %span.hidden-xs
+        in
+        = link_to namespace_project_path(snippet.project.namespace, snippet.project) do
+          = snippet.project.name_with_namespace
+
+    .pull-right.snippet-updated-at
+      %span updated #{time_ago_with_tooltip(snippet.updated_at, placement: 'bottom')}
diff --git a/app/views/snippets/_actions.html.haml b/app/views/snippets/_actions.html.haml
index 1d0e549ed3d64ec891ec5fcb5b90481c66758c89..95fc71981044a0253152391a8cd231ce61d32891 100644
--- a/app/views/snippets/_actions.html.haml
+++ b/app/views/snippets/_actions.html.haml
@@ -1,13 +1,13 @@
 .hidden-xs
-  - if current_user
-    = link_to new_snippet_path, class: "btn btn-grouped btn-create new-snippet-link", title: "New snippet" do
-      New snippet
-  - if can?(current_user, :admin_personal_snippet, @snippet)
-    = link_to snippet_path(@snippet), method: :delete, data: { confirm: "Are you sure?" }, class: "btn btn-grouped btn-danger", title: 'Delete Snippet' do
-      Delete
   - if can?(current_user, :update_personal_snippet, @snippet)
-    = link_to edit_snippet_path(@snippet), class: "btn btn-grouped snippable-edit" do
+    = link_to edit_snippet_path(@snippet), class: "btn btn-grouped" do
       Edit
+  - if can?(current_user, :admin_personal_snippet, @snippet)
+    = link_to snippet_path(@snippet), method: :delete, data: { confirm: "Are you sure?" }, class: "btn btn-grouped btn-inverted btn-remove", title: 'Delete Snippet' do
+      Delete
+  - if current_user
+    = link_to new_snippet_path, class: "btn btn-grouped btn-inverted btn-create", title: "New snippet" do
+      New snippet
 - if current_user
   .visible-xs-block.dropdown
     %button.btn.btn-default.btn-block.append-bottom-0.prepend-top-5{ data: { toggle: "dropdown" } }
diff --git a/app/views/snippets/_snippets.html.haml b/app/views/snippets/_snippets.html.haml
index 77b66ca74b6385ac55c3b9d8dc22e4d2c41beb21..ac3701233ad1e8411391a9c190a65c3ec2e40462 100644
--- a/app/views/snippets/_snippets.html.haml
+++ b/app/views/snippets/_snippets.html.haml
@@ -1,8 +1,9 @@
 - remote = local_assigns.fetch(:remote, false)
+- link_project = local_assigns.fetch(:link_project, false)
 
 .snippets-list-holder
   %ul.content-list
-    = render partial: 'shared/snippets/snippet', collection: @snippets
+    = render partial: 'shared/snippets/snippet', collection: @snippets, locals: { link_project: link_project }
     - if @snippets.empty?
       %li
         .nothing-here-block Nothing here.
diff --git a/app/views/snippets/_snippets_scope_menu.html.haml b/app/views/snippets/_snippets_scope_menu.html.haml
new file mode 100644
index 0000000000000000000000000000000000000000..2dda5fed64794221d31c1cea59a480cd9b9a9faa
--- /dev/null
+++ b/app/views/snippets/_snippets_scope_menu.html.haml
@@ -0,0 +1,31 @@
+- subject = local_assigns.fetch(:subject, current_user)
+- include_private = local_assigns.fetch(:include_private, false)
+
+.nav-links.snippet-scope-menu
+  %li{ class: ("active" unless params[:scope]) }
+    = link_to subject_snippets_path(subject) do
+      All
+      %span.badge
+        - if include_private
+          = subject.snippets.count
+        - else
+          = subject.snippets.public_and_internal.count
+
+  - if include_private
+    %li{ class: ("active" if params[:scope] == "are_private") }
+      = link_to subject_snippets_path(subject, scope: 'are_private') do
+        Private
+        %span.badge
+          = subject.snippets.are_private.count
+
+  %li{ class: ("active" if params[:scope] == "are_internal") }
+    = link_to subject_snippets_path(subject, scope: 'are_internal') do
+      Internal
+      %span.badge
+        = subject.snippets.are_internal.count
+
+  %li{ class: ("active" if params[:scope] == "are_public") }
+    = link_to subject_snippets_path(subject, scope: 'are_public') do
+      Public
+      %span.badge
+        = subject.snippets.are_public.count
diff --git a/changelogs/unreleased/19550-fix-contributer-graph-duplicates.yml b/changelogs/unreleased/19550-fix-contributer-graph-duplicates.yml
new file mode 100644
index 0000000000000000000000000000000000000000..742b10e72aa67152e3daae28eb4620b48fdc8072
--- /dev/null
+++ b/changelogs/unreleased/19550-fix-contributer-graph-duplicates.yml
@@ -0,0 +1,4 @@
+---
+title: group authors in contribution graph with case insensitive email handle comparison
+merge_request: 8021
+author:
diff --git a/changelogs/unreleased/20052-actions-table-vscroll.yml b/changelogs/unreleased/20052-actions-table-vscroll.yml
new file mode 100644
index 0000000000000000000000000000000000000000..779cd08de0968c327a087e57a4bd8eec51adac6a
--- /dev/null
+++ b/changelogs/unreleased/20052-actions-table-vscroll.yml
@@ -0,0 +1,4 @@
+---
+title: Prevent overflow with vertical scroll when we have space to show content
+merge_request: 8061
+author:
diff --git a/changelogs/unreleased/24807-stop-ddosing-ourselves.yml b/changelogs/unreleased/24807-stop-ddosing-ourselves.yml
new file mode 100644
index 0000000000000000000000000000000000000000..49e6c5e56e598741d478a98120f56c88547c0c9c
--- /dev/null
+++ b/changelogs/unreleased/24807-stop-ddosing-ourselves.yml
@@ -0,0 +1,4 @@
+---
+title: Use SmartInterval for MR widget and improve visibilitychange functionality
+merge_request: 7762
+author: 
diff --git a/changelogs/unreleased/24949-view-2-up-swipe-onion-skin-controls-for-merge-request-diff-containing-an-image.yml b/changelogs/unreleased/24949-view-2-up-swipe-onion-skin-controls-for-merge-request-diff-containing-an-image.yml
new file mode 100644
index 0000000000000000000000000000000000000000..b8ba9391530beaa0fccfaba04faa438a87e04a55
--- /dev/null
+++ b/changelogs/unreleased/24949-view-2-up-swipe-onion-skin-controls-for-merge-request-diff-containing-an-image.yml
@@ -0,0 +1,4 @@
+---
+title: Add image controls to MR diffs
+merge_request: 7919
+author: 
diff --git a/changelogs/unreleased/25106-hide-issue-mr-button-for-not-loggedin.yml b/changelogs/unreleased/25106-hide-issue-mr-button-for-not-loggedin.yml
new file mode 100644
index 0000000000000000000000000000000000000000..62030d3fc455f85b16a06ac82988171a0550c4df
--- /dev/null
+++ b/changelogs/unreleased/25106-hide-issue-mr-button-for-not-loggedin.yml
@@ -0,0 +1,4 @@
+---
+title: Prevent user creating issue or MR without signing in for a group
+merge_request: 7902
+author:
diff --git a/changelogs/unreleased/25136-last-deployment-link.yml b/changelogs/unreleased/25136-last-deployment-link.yml
new file mode 100644
index 0000000000000000000000000000000000000000..eab1534aa6688a8df38ed43da1630a3a47ae3748
--- /dev/null
+++ b/changelogs/unreleased/25136-last-deployment-link.yml
@@ -0,0 +1,4 @@
+---
+title: Fix Latest deployment link is broken
+merge_request: 7839
+author:
diff --git a/changelogs/unreleased/25294-remove-signed-out-msg.yml b/changelogs/unreleased/25294-remove-signed-out-msg.yml
new file mode 100644
index 0000000000000000000000000000000000000000..567294fe5f7940e3e62d2701a4be561fa8920507
--- /dev/null
+++ b/changelogs/unreleased/25294-remove-signed-out-msg.yml
@@ -0,0 +1,4 @@
+---
+title: 'fix: removed signed_out notification'
+merge_request: 7958
+author: jnoortheen
diff --git a/changelogs/unreleased/25482-fix-api-sudo.yml b/changelogs/unreleased/25482-fix-api-sudo.yml
new file mode 100644
index 0000000000000000000000000000000000000000..4c11fe1622eb5ad3d2ce499e3936267b46e2db16
--- /dev/null
+++ b/changelogs/unreleased/25482-fix-api-sudo.yml
@@ -0,0 +1,4 @@
+---
+title: 'API: Memoize the current_user so that sudo can work properly'
+merge_request: 8017
+author:
diff --git a/changelogs/unreleased/25483-broken-tabs.yml b/changelogs/unreleased/25483-broken-tabs.yml
new file mode 100644
index 0000000000000000000000000000000000000000..d6c92014bea37caf8be3e28f481cb87523b8010b
--- /dev/null
+++ b/changelogs/unreleased/25483-broken-tabs.yml
@@ -0,0 +1,4 @@
+---
+title: Fix TypeError: Cannot read property 'initTabs' on commit builds tab
+merge_request: 8009
+author:
diff --git a/changelogs/unreleased/allow-more-filenames.yml b/changelogs/unreleased/allow-more-filenames.yml
new file mode 100644
index 0000000000000000000000000000000000000000..7989f94e528dda8b1895d02870d6185f8becf71c
--- /dev/null
+++ b/changelogs/unreleased/allow-more-filenames.yml
@@ -0,0 +1,4 @@
+---
+title: Allow all alphanumeric characters in file names
+merge_request: 8002
+author: winniehell
diff --git a/changelogs/unreleased/api-cherry-pick.yml b/changelogs/unreleased/api-cherry-pick.yml
new file mode 100644
index 0000000000000000000000000000000000000000..5f4cee450b9adf081d26c1ffcc572dbc82fbcbe6
--- /dev/null
+++ b/changelogs/unreleased/api-cherry-pick.yml
@@ -0,0 +1,4 @@
+---
+title: 'API: Ability to cherry pick a commit'
+merge_request: 8047
+author: Robert Schilling
diff --git a/changelogs/unreleased/api-simple-group-project.yml b/changelogs/unreleased/api-simple-group-project.yml
new file mode 100644
index 0000000000000000000000000000000000000000..54c8de610a6c09c045b0389d1ed49616206150fa
--- /dev/null
+++ b/changelogs/unreleased/api-simple-group-project.yml
@@ -0,0 +1,4 @@
+---
+title: 'API: Simple representation of group''s projects'
+merge_request: 8060
+author: Robert Schilling
diff --git a/changelogs/unreleased/awards_handler.yml b/changelogs/unreleased/awards_handler.yml
new file mode 100644
index 0000000000000000000000000000000000000000..1f9904c069172cc816582e4058d1efe405e7f6be
--- /dev/null
+++ b/changelogs/unreleased/awards_handler.yml
@@ -0,0 +1,4 @@
+---
+title: Replace static fixture for awards_handler_spec
+merge_request: 7661
+author: winniehell
diff --git a/changelogs/unreleased/chomp-git-status-message.yml b/changelogs/unreleased/chomp-git-status-message.yml
new file mode 100644
index 0000000000000000000000000000000000000000..f70607df7a18b4cbc85ab6fb00cdffe7184d86c0
--- /dev/null
+++ b/changelogs/unreleased/chomp-git-status-message.yml
@@ -0,0 +1,5 @@
+---
+title: For single line git commit messages, the close quote should be on the same
+  line as the open quote
+merge_request:
+author:
diff --git a/changelogs/unreleased/features-api-snippets.yml b/changelogs/unreleased/features-api-snippets.yml
new file mode 100644
index 0000000000000000000000000000000000000000..80c7bb753594eeb5bbb62d1ec690bfd6d0eafb5d
--- /dev/null
+++ b/changelogs/unreleased/features-api-snippets.yml
@@ -0,0 +1,4 @@
+---
+title: 'API: Endpoint to expose personal snippets as /snippets'
+merge_request: 6373
+author: Bernard Guyzmo Pratz
diff --git a/changelogs/unreleased/issue_13270.yml b/changelogs/unreleased/issue_13270.yml
new file mode 100644
index 0000000000000000000000000000000000000000..9c15c4368769af51b184758f846e5d0580921ffc
--- /dev/null
+++ b/changelogs/unreleased/issue_13270.yml
@@ -0,0 +1,4 @@
+---
+title: Allow to delete tag release note
+merge_request: 
+author: 
diff --git a/changelogs/unreleased/issue_25030.yml b/changelogs/unreleased/issue_25030.yml
new file mode 100644
index 0000000000000000000000000000000000000000..e18b8d6a79b9f8146622c043ec28d2494fc55d48
--- /dev/null
+++ b/changelogs/unreleased/issue_25030.yml
@@ -0,0 +1,4 @@
+---
+title: Allow branch names with dots on API endpoint
+merge_request: 
+author: 
diff --git a/changelogs/unreleased/process-commit-worker-migration-encoding.yml b/changelogs/unreleased/process-commit-worker-migration-encoding.yml
new file mode 100644
index 0000000000000000000000000000000000000000..26aabd9b647d321512b278284e77d73b1cca98e2
--- /dev/null
+++ b/changelogs/unreleased/process-commit-worker-migration-encoding.yml
@@ -0,0 +1,4 @@
+---
+title: Encode input when migrating ProcessCommitWorker jobs to prevent migration errors
+merge_request: 
+author: 
diff --git a/changelogs/unreleased/unescape-relative-path.yml b/changelogs/unreleased/unescape-relative-path.yml
new file mode 100644
index 0000000000000000000000000000000000000000..755b0379a16bd2917b09c08e205450046ac4b6c0
--- /dev/null
+++ b/changelogs/unreleased/unescape-relative-path.yml
@@ -0,0 +1,4 @@
+---
+title: Avoid escaping relative links in Markdown twice
+merge_request: 7940
+author: winniehell
diff --git a/config/initializers/1_settings.rb b/config/initializers/1_settings.rb
index 9ddd15548111aa78dd7457ece5e8a4be3ef1f694..0ee1b1ec6344eca9855c26b4fbf811deab222bcb 100644
--- a/config/initializers/1_settings.rb
+++ b/config/initializers/1_settings.rb
@@ -302,7 +302,7 @@ Settings.cron_jobs['remove_expired_group_links_worker'] ||= Settingslogic.new({}
 Settings.cron_jobs['remove_expired_group_links_worker']['cron'] ||= '10 0 * * *'
 Settings.cron_jobs['remove_expired_group_links_worker']['job_class'] = 'RemoveExpiredGroupLinksWorker'
 Settings.cron_jobs['prune_old_events_worker'] ||= Settingslogic.new({})
-Settings.cron_jobs['prune_old_events_worker']['cron'] ||= '* */6 * * *'
+Settings.cron_jobs['prune_old_events_worker']['cron'] ||= '0 */6 * * *'
 Settings.cron_jobs['prune_old_events_worker']['job_class'] = 'PruneOldEventsWorker'
 
 Settings.cron_jobs['trending_projects_worker'] ||= Settingslogic.new({})
diff --git a/config/routes.rb b/config/routes.rb
index 03b47261e7e6210740cbe00a5ee3ae65edd9af40..06d565df469ca28351e98e3318ac6c365daa8813 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -1,6 +1,5 @@
 require 'sidekiq/web'
 require 'sidekiq/cron/web'
-require 'api/api'
 require 'constraints/group_url_constrainer'
 
 Rails.application.routes.draw do
diff --git a/config/routes/admin.rb b/config/routes/admin.rb
index 5ae985da56107ab0ad465af2710b763a771b7849..0dd2c8f7aef7df4646c3710da07874bcacbc8739 100644
--- a/config/routes/admin.rb
+++ b/config/routes/admin.rb
@@ -28,9 +28,19 @@ namespace :admin do
 
   resources :applications
 
-  resources :groups, constraints: { id: /[^\/]+/ } do
-    member do
+  resources :groups, only: [:index, :new, :create]
+
+  scope(path: 'groups/*id',
+        controller: :groups,
+        constraints: { id: Gitlab::Regex.namespace_route_regex }) do
+
+    scope(as: :group) do
       put :members_update
+      get :edit, action: :edit
+      get '/', action: :show
+      patch '/', action: :update
+      put '/', action: :update
+      delete '/', action: :destroy
     end
   end
 
@@ -50,14 +60,13 @@ namespace :admin do
   resource :system_info, controller: 'system_info', only: [:show]
   resources :requests_profiles, only: [:index, :show], param: :name, constraints: { name: /.+\.html/ }
 
-  resources :namespaces, path: '/projects', constraints: { id: /[a-zA-Z.0-9_\-]+/ }, only: [] do
-    root to: 'projects#index', as: :projects
+  resources :projects, only: [:index]
 
+  scope(path: 'projects/*namespace_id', as: :namespace) do
     resources(:projects,
               path: '/',
-              constraints: { id: /[a-zA-Z.0-9_\-]+/ },
-              only: [:index, :show]) do
-      root to: 'projects#show'
+              constraints: { id: Gitlab::Regex.project_route_regex },
+              only: [:show]) do
 
       member do
         put :transfer
diff --git a/db/migrate/20161124141322_migrate_process_commit_worker_jobs.rb b/db/migrate/20161124141322_migrate_process_commit_worker_jobs.rb
index 453a44e271ac9561197b075fbc475e9cd9187660..77e0c40d850c2b4884f06c6ef13baa256a15d119 100644
--- a/db/migrate/20161124141322_migrate_process_commit_worker_jobs.rb
+++ b/db/migrate/20161124141322_migrate_process_commit_worker_jobs.rb
@@ -47,14 +47,14 @@ class MigrateProcessCommitWorkerJobs < ActiveRecord::Migration
 
         hash = {
           id: commit.oid,
-          message: commit.message,
+          message: encode(commit.message),
           parent_ids: commit.parent_ids,
           authored_date: commit.author[:time],
-          author_name: commit.author[:name],
-          author_email: commit.author[:email],
+          author_name: encode(commit.author[:name]),
+          author_email: encode(commit.author[:email]),
           committed_date: commit.committer[:time],
-          committer_email: commit.committer[:email],
-          committer_name: commit.committer[:name]
+          committer_email: encode(commit.committer[:email]),
+          committer_name: encode(commit.committer[:name])
         }
 
         payload['args'][2] = hash
@@ -89,4 +89,14 @@ class MigrateProcessCommitWorkerJobs < ActiveRecord::Migration
       end
     end
   end
+
+  def encode(data)
+    encoding = Encoding::UTF_8
+
+    if data.encoding == encoding
+      data
+    else
+      data.encode(encoding, invalid: :replace, undef: :replace)
+    end
+  end
 end
diff --git a/db/migrate/20161212142807_add_lower_path_index_to_routes.rb b/db/migrate/20161212142807_add_lower_path_index_to_routes.rb
new file mode 100644
index 0000000000000000000000000000000000000000..6958500306f925eaed4769ecc42fb4b3b8cf36d9
--- /dev/null
+++ b/db/migrate/20161212142807_add_lower_path_index_to_routes.rb
@@ -0,0 +1,22 @@
+# See http://doc.gitlab.com/ce/development/migration_style_guide.html
+# for more information on how to write migrations for GitLab.
+
+class AddLowerPathIndexToRoutes < ActiveRecord::Migration
+  include Gitlab::Database::MigrationHelpers
+
+  DOWNTIME = false
+
+  disable_ddl_transaction!
+
+  def up
+    return unless Gitlab::Database.postgresql?
+
+    execute 'CREATE INDEX CONCURRENTLY index_on_routes_lower_path ON routes (LOWER(path));'
+  end
+
+  def down
+    return unless Gitlab::Database.postgresql?
+
+    remove_index :routes, name: :index_on_routes_lower_path
+  end
+end
diff --git a/db/schema.rb b/db/schema.rb
index 08b1590e48455c0fda51b852c0b9ac26dfd9bf0a..4711b7873af376f886fa6d2a8dd9f51dc32ae1fb 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -11,7 +11,7 @@
 #
 # It's strongly recommended that you check this file into your version control system.
 
-ActiveRecord::Schema.define(version: 20161206153754) do
+ActiveRecord::Schema.define(version: 20161212142807) do
 
   # These are extensions that must be enabled in order to support this database
   enable_extension "plpgsql"
@@ -1290,4 +1290,4 @@ ActiveRecord::Schema.define(version: 20161206153754) do
   add_foreign_key "subscriptions", "projects", on_delete: :cascade
   add_foreign_key "trending_projects", "projects", on_delete: :cascade
   add_foreign_key "u2f_registrations", "users"
-end
+end
\ No newline at end of file
diff --git a/doc/README.md b/doc/README.md
index 66c8c26e4f0611291781f7bc55e386d58cdabdde..eba1e9845b10fe3fd986ddabd49c45a56d9aad3f 100644
--- a/doc/README.md
+++ b/doc/README.md
@@ -1,4 +1,4 @@
-# Documentation
+# GitLab Community Edition documentation
 
 ## User documentation
 
diff --git a/doc/api/commits.md b/doc/api/commits.md
index 0170af00e0ebe9466162c3f8d2465fb850e2ea7a..5c11d0f83bbd6f78cabcb8380438738a65c28597 100644
--- a/doc/api/commits.md
+++ b/doc/api/commits.md
@@ -183,6 +183,44 @@ Example response:
 }
 ```
 
+## Cherry pick a commit
+
+> [Introduced][ce-8047] in GitLab 8.15.
+
+Cherry picks a commit to a given branch.
+
+```
+POST /projects/:id/repository/commits/:sha/cherry_pick
+```
+
+Parameters:
+
+| Attribute | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `id`      | integer/string | yes | The ID of a project or NAMESPACE/PROJECT_NAME owned by the authenticated user
+| `sha` | string | yes | The commit hash  |
+| `branch` | string | yes | The name of the branch  |
+
+```bash
+curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" --form "branch=master" "https://gitlab.example.com/api/v3/projects/5/repository/commits/master/cherry_pick"
+```
+
+Example response:
+
+```json
+{
+  "id": "8b090c1b79a14f2bd9e8a738f717824ff53aebad",
+  "short_id": "8b090c1b",
+  "title": "Feature added",
+  "author_name": "Dmitriy Zaporozhets",
+  "author_email": "dmitriy.zaporozhets@gmail.com",
+  "created_at": "2016-12-12T20:10:39.000+01:00",
+  "committer_name": "Administrator",
+  "committer_email": "admin@example.com",
+  "message": "Feature added\n\nSigned-off-by: Dmitriy Zaporozhets <dmitriy.zaporozhets@gmail.com>\n"
+}
+```
+
 ## Get the diff of a commit
 
 Get the diff of a commit in a project.
@@ -438,3 +476,4 @@ Example response:
 ```
 
 [ce-6096]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/6096 "Multi-file commit"
+[ce-8047]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/8047
diff --git a/doc/api/groups.md b/doc/api/groups.md
index 5e6f498c365ff770b4dbf33b7458cffdedecc45c..134d7bda22f19702441cbdd0d72fd391537b6444 100644
--- a/doc/api/groups.md
+++ b/doc/api/groups.md
@@ -50,12 +50,17 @@ GET /groups/:id/projects
 
 Parameters:
 
-- `archived` (optional) - if passed, limit by archived status
-- `visibility` (optional) - if passed, limit by visibility `public`, `internal`, `private`
-- `order_by` (optional) - Return requests ordered by `id`, `name`, `path`, `created_at`, `updated_at` or `last_activity_at` fields. Default is `created_at`
-- `sort` (optional) - Return requests sorted in `asc` or `desc` order. Default is `desc`
-- `search` (optional) - Return list of authorized projects according to a search criteria
-- `ci_enabled_first` - Return projects ordered by ci_enabled flag. Projects with enabled GitLab CI go first
+| Attribute | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `id` | integer/string | yes | The ID or path of a group |
+| `archived` | boolean | no | Limit by archived status |
+| `visibility` | string | no | Limit by visibility `public`, `internal`, or `private` |
+| `order_by` | string | no | Return projects ordered by `id`, `name`, `path`, `created_at`, `updated_at`, or `last_activity_at` fields. Default is `created_at` |
+| `sort` | string | no | Return projects sorted in `asc` or `desc` order. Default is `desc` |
+| `search` | string | no | Return list of authorized projects matching the search criteria |
+| `simple` | boolean | no | Return only the ID, URL, name, and path of each project |
+
+Example response:
 
 ```json
 [
diff --git a/doc/api/merge_requests.md b/doc/api/merge_requests.md
index 81df55ab4aba348dfa4fb2b7b9bdc2433a3f8b9f..662cc9da7335e06cc5516de164359d861e35ac27 100644
--- a/doc/api/merge_requests.md
+++ b/doc/api/merge_requests.md
@@ -429,7 +429,7 @@ DELETE /projects/:id/merge_requests/:merge_request_id
 | `merge_request_id` | integer | yes | The ID of a project's merge request |
 
 ```bash
-curl --request DELETE --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/4/merge_request/85
+curl --request DELETE --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/4/merge_requests/85
 ```
 
 ## Accept MR
diff --git a/doc/api/services.md b/doc/api/services.md
index a5d733fe6c7ebebc6e2833bc9087b42e41225c0a..3dad953cd1e0e60ef6087ae50749f1605cc87d69 100644
--- a/doc/api/services.md
+++ b/doc/api/services.md
@@ -139,6 +139,43 @@ Get Buildkite service settings for a project.
 GET /projects/:id/services/buildkite
 ```
 
+## Build-Emails
+
+Get emails for GitLab CI builds.
+
+### Create/Edit Build-Emails service
+
+Set Build-Emails service for a project.
+
+```
+PUT /projects/:id/services/builds-email
+```
+
+Parameters:
+
+| Attribute | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `recipients` | string | yes | Comma-separated list of recipient email addresses |
+| `add_pusher` | boolean | no | Add pusher to recipients list |
+| `notify_only_broken_builds` | boolean | no | Notify only broken builds |
+
+
+### Delete Build-Emails service
+
+Delete Build-Emails service for a project.
+
+```
+DELETE /projects/:id/services/builds-email
+```
+
+### Get Build-Emails service settings
+
+Get Build-Emails service settings for a project.
+
+```
+GET /projects/:id/services/builds-email
+```
+
 ## Campfire
 
 Simple web-based real-time group chat
@@ -476,12 +513,11 @@ PUT /projects/:id/services/jira
 
 | Attribute | Type | Required | Description |
 | --------- | ---- | -------- | ----------- |
-| `active`        | boolean| no  | Enable/disable the JIRA service. |
 | `url`           | string | yes | The URL to the JIRA project which is being linked to this GitLab project, e.g., `https://jira.example.com`. |
 | `project_key`   | string | yes | The short identifier for your JIRA project, all uppercase, e.g., `PROJ`. |
 | `username`      | string | no  | The username of the user created to be used with GitLab/JIRA. |
 | `password`      | string | no  | The password of the user created to be used with GitLab/JIRA. |
-| `jira_issue_transition_id` | string | no | The ID of a transition that moves issues to a closed state. You can find this number under the JIRA workflow administration (**Administration > Issues > Workflows**) by selecting **View** under **Operations** of the desired workflow of your project. The ID of each state can be found inside the parenthesis of each transition name under the **Transitions (id)** column ([see screenshot][trans]). By default, this ID is set to `2`. |
+| `jira_issue_transition_id` | integer | no | The ID of a transition that moves issues to a closed state. You can find this number under the JIRA workflow administration (**Administration > Issues > Workflows**) by selecting **View** under **Operations** of the desired workflow of your project. The ID of each state can be found inside the parenthesis of each transition name under the **Transitions (id)** column ([see screenshot][trans]). By default, this ID is set to `2`. |
 
 ### Delete JIRA service
 
@@ -491,6 +527,78 @@ Remove all previously JIRA settings from a project.
 DELETE /projects/:id/services/jira
 ```
 
+## Mattermost Slash Commands
+
+Ability to receive slash commands from a Mattermost chat instance.
+
+### Create/Edit Mattermost Slash Command service
+
+Set Mattermost Slash Command for a project.
+
+```
+PUT /projects/:id/services/mattermost-slash-commands
+```
+
+Parameters:
+
+| Attribute | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `token` | string | yes | The Mattermost token |
+
+
+### Delete Mattermost Slash Command service
+
+Delete Mattermost Slash Command service for a project.
+
+```
+DELETE /projects/:id/services/mattermost-slash-commands
+```
+
+### Get Mattermost Slash Command service settings
+
+Get Mattermost Slash Command service settings for a project.
+
+```
+GET /projects/:id/services/mattermost-slash-commands
+```
+
+## Pipeline-Emails
+
+Get emails for GitLab CI pipelines.
+
+### Create/Edit Pipeline-Emails service
+
+Set Pipeline-Emails service for a project.
+
+```
+PUT /projects/:id/services/pipelines-email
+```
+
+Parameters:
+
+| Attribute | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `recipients` | string | yes | Comma-separated list of recipient email addresses |
+| `add_pusher` | boolean | no | Add pusher to recipients list |
+| `notify_only_broken_builds` | boolean | no | Notify only broken pipelines |
+
+
+### Delete Pipeline-Emails service
+
+Delete Pipeline-Emails service for a project.
+
+```
+DELETE /projects/:id/services/pipelines-email
+```
+
+### Get Pipeline-Emails service settings
+
+Get Pipeline-Emails service settings for a project.
+
+```
+GET /projects/:id/services/pipelines-email
+```
+
 ## PivotalTracker
 
 Project Management Software (Source Commits Endpoint)
diff --git a/doc/api/snippets.md b/doc/api/snippets.md
new file mode 100644
index 0000000000000000000000000000000000000000..5a5dc162ffe29f5feaa974c104db204492c30eee
--- /dev/null
+++ b/doc/api/snippets.md
@@ -0,0 +1,232 @@
+# Snippets
+
+> [Introduced][ce-6373] in GitLab 8.15.
+
+### Snippet visibility level
+
+Snippets in GitLab can be either private, internal, or public.
+You can set it with the `visibility_level` field in the snippet.
+
+Constants for snippet visibility levels are:
+
+| Visibility | Visibility level | Description |
+| ---------- | ---------------- | ----------- |
+| Private    | `0`  | The snippet is visible only to the snippet creator |
+| Internal   | `10` | The snippet is visible for any logged in user |
+| Public     | `20` | The snippet can be accessed without any authentication |
+
+## List snippets
+
+Get a list of current user's snippets.
+
+```
+GET /snippets
+```
+
+## Single snippet
+
+Get a single snippet.
+
+```
+GET /snippets/:id
+```
+
+Parameters:
+
+| Attribute          | Type    | Required | Description                   |
+| ---------          | ----    | -------- | -----------                   |
+| `id`               | Integer | yes      | The ID of a snippet           |
+
+``` bash
+curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/snippets/1
+```
+
+Example response:
+
+``` json
+{
+  "id": 1,
+  "title": "test",
+  "file_name": "add.rb",
+  "author": {
+    "id": 1,
+    "username": "john_smith",
+    "email": "john@example.com",
+    "name": "John Smith",
+    "state": "active",
+    "created_at": "2012-05-23T08:00:58Z"
+  },
+  "expires_at": null,
+  "updated_at": "2012-06-28T10:52:04Z",
+  "created_at": "2012-06-28T10:52:04Z",
+  "web_url": "http://example.com/snippets/1",
+}
+```
+
+## Create new snippet
+
+Creates a new snippet. The user must have permission to create new snippets.
+
+```
+POST /snippets
+```
+
+Parameters:
+
+| Attribute          | Type    | Required | Description                |
+| ---------          | ----    | -------- | -----------                |
+| `title`            | String  | yes      | The title of a snippet     |
+| `file_name`        | String  | yes      | The name of a snippet file |
+| `content`          | String  | yes      | The content of a snippet   |
+| `visibility_level` | Integer | yes      | The snippet's visibility   |
+
+
+``` bash
+curl --request POST --data '{"title": "This is a snippet", "content": "Hello world", "file_name": "test.txt", "visibility_level": 10 }' --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/snippets
+```
+
+Example response:
+
+``` json
+{
+  "id": 1,
+  "title": "This is a snippet",
+  "file_name": "test.txt",
+  "author": {
+    "id": 1,
+    "username": "john_smith",
+    "email": "john@example.com",
+    "name": "John Smith",
+    "state": "active",
+    "created_at": "2012-05-23T08:00:58Z"
+  },
+  "expires_at": null,
+  "updated_at": "2012-06-28T10:52:04Z",
+  "created_at": "2012-06-28T10:52:04Z",
+  "web_url": "http://example.com/snippets/1",
+}
+```
+
+## Update snippet
+
+Updates an existing snippet. The user must have permission to change an existing snippet.
+
+```
+PUT /snippets/:id
+```
+
+Parameters:
+
+| Attribute          | Type    | Required | Description                |
+| ---------          | ----    | -------- | -----------                |
+| `id`               | Integer | yes      | The ID of a snippet        |
+| `title`            | String  | no       | The title of a snippet     |
+| `file_name`        | String  | no       | The name of a snippet file |
+| `content`          | String  | no       | The content of a snippet   |
+| `visibility_level` | Integer | no       | The snippet's visibility   |
+
+
+``` bash
+curl --request PUT --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" --data '{"title": "foo", "content": "bar"}' https://gitlab.example.com/api/v3/snippets/1
+```
+
+Example response:
+
+``` json
+{
+  "id": 1,
+  "title": "test",
+  "file_name": "add.rb",
+  "author": {
+    "id": 1,
+    "username": "john_smith",
+    "email": "john@example.com",
+    "name": "John Smith",
+    "state": "active",
+    "created_at": "2012-05-23T08:00:58Z"
+  },
+  "expires_at": null,
+  "updated_at": "2012-06-28T10:52:04Z",
+  "created_at": "2012-06-28T10:52:04Z",
+  "web_url": "http://example.com/snippets/1",
+}
+```
+
+## Delete snippet
+
+Deletes an existing snippet. 
+
+```
+DELETE /snippets/:id
+```
+
+Parameters:
+
+| Attribute          | Type    | Required | Description                   |
+| ---------          | ----    | -------- | -----------                   |
+| `id`               | Integer | yes      | The ID of a snippet           |
+
+
+```
+curl --request DELETE --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/snippets/1"
+```
+
+upon successful delete a `204 No content` HTTP code shall be expected, with no data,
+but if the snippet is non-existent, a `404 Not Found` will be returned.
+
+## Explore all public snippets
+
+```
+GET /snippets/public
+```
+
+| Attribute  | Type    | Required | Description                           |
+| ---------  | ----    | -------- | -----------                           |
+| `per_page` | Integer | no       | number of snippets to return per page |
+| `page`     | Integer | no       | the page to retrieve                  |
+
+``` bash
+curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/snippets/public?per_page=2&page=1
+```
+
+Example response:
+
+``` json
+[
+    {
+        "author": {
+            "avatar_url": "http://www.gravatar.com/avatar/edaf55a9e363ea263e3b981d09e0f7f7?s=80&d=identicon",
+            "id": 12,
+            "name": "Libby Rolfson",
+            "state": "active",
+            "username": "elton_wehner",
+            "web_url": "http://localhost:3000/elton_wehner"
+        },
+        "created_at": "2016-11-25T16:53:34.504Z",
+        "file_name": "oconnerrice.rb",
+        "id": 49,
+        "raw_url": "http://localhost:3000/snippets/49/raw",
+        "title": "Ratione cupiditate et laborum temporibus.",
+        "updated_at": "2016-11-25T16:53:34.504Z",
+        "web_url": "http://localhost:3000/snippets/49"
+    },
+    {
+        "author": {
+            "avatar_url": "http://www.gravatar.com/avatar/36583b28626de71061e6e5a77972c3bd?s=80&d=identicon",
+            "id": 16,
+            "name": "Llewellyn Flatley",
+            "state": "active",
+            "username": "adaline",
+            "web_url": "http://localhost:3000/adaline"
+        },
+        "created_at": "2016-11-25T16:53:34.479Z",
+        "file_name": "muellershields.rb",
+        "id": 48,
+        "raw_url": "http://localhost:3000/snippets/48/raw",
+        "title": "Minus similique nesciunt vel fugiat qui ullam sunt.",
+        "updated_at": "2016-11-25T16:53:34.479Z",
+        "web_url": "http://localhost:3000/snippets/48"
+    }
+]
+```
+
diff --git a/doc/ci/review_apps/index.md b/doc/ci/review_apps/index.md
index a66165dc973b2c6e50bc8d196568e0d01bf1cd23..c679ea4e2982c5e8e5d6afaea8ded3eed6791ffe 100644
--- a/doc/ci/review_apps/index.md
+++ b/doc/ci/review_apps/index.md
@@ -33,7 +33,7 @@ built and deployed under a dynamic environment and can be previewed with an
 also dynamically URL.
 
 The details of the Review Apps implementation depend widely on your real
-technology stack and on your deployment process. The simplest case it to
+technology stack and on your deployment process. The simplest case is to
 deploy a simple static HTML website, but it will not be that straightforward
 when your app is using a database for example. To make a branch be deployed
 on a temporary instance and booting up this instance with all required software
diff --git a/doc/update/8.13-to-8.14.md b/doc/update/8.13-to-8.14.md
index a0e895773ce002c146480334ab2f3a25c2dff376..c64d34074616c41898567440189d47bd4795a1ce 100644
--- a/doc/update/8.13-to-8.14.md
+++ b/doc/update/8.13-to-8.14.md
@@ -72,7 +72,7 @@ sudo -u git -H git checkout 8-14-stable-ee
 ```bash
 cd /home/git/gitlab-shell
 sudo -u git -H git fetch --all --tags
-sudo -u git -H git checkout v4.0.0
+sudo -u git -H git checkout v4.0.3
 ```
 
 ### 6. Update gitlab-workhorse
diff --git a/features/admin/settings.feature b/features/admin/settings.feature
deleted file mode 100644
index e38eea2cfed7cbbe63f139310a35659117041ec6..0000000000000000000000000000000000000000
--- a/features/admin/settings.feature
+++ /dev/null
@@ -1,19 +0,0 @@
-@admin
-Feature: Admin Settings
-  Background:
-    Given I sign in as an admin
-    And I visit admin settings page
-
-  Scenario: Change application settings
-    When I modify settings and save form
-    Then I should see application settings saved
-
-  Scenario: Change Slack Service Template settings
-    When I click on "Service Templates"
-    And I click on "Slack" service
-    And I fill out Slack settings
-    Then I check all events and submit form
-    And I should see service template settings saved
-    Then I click on "Slack" service
-    And I should see all checkboxes checked
-    And I should see Slack settings saved
diff --git a/features/steps/project/snippets.rb b/features/steps/project/snippets.rb
index 5e7d539add6c181be89681d7597eb198297d4fe1..a3bebfa4b715052a3130553c640f598da768db11 100644
--- a/features/steps/project/snippets.rb
+++ b/features/steps/project/snippets.rb
@@ -22,7 +22,7 @@ class Spinach::Features::ProjectSnippets < Spinach::FeatureSteps
   end
 
   step 'I click link "New snippet"' do
-    click_link "New snippet"
+    first(:link, "New snippet").click
   end
 
   step 'I click link "Snippet one"' do
diff --git a/features/steps/shared/paths.rb b/features/steps/shared/paths.rb
index 2bd8ea745e47abadca0472a99eb3e09533838a94..82c07d4f536ed7fefbed75a9070868d774b7d256 100644
--- a/features/steps/shared/paths.rb
+++ b/features/steps/shared/paths.rb
@@ -168,7 +168,7 @@ module SharedPaths
   end
 
   step 'I visit admin projects page' do
-    visit admin_namespaces_projects_path
+    visit admin_projects_path
   end
 
   step 'I visit admin users page' do
@@ -203,10 +203,6 @@ module SharedPaths
     visit admin_teams_path
   end
 
-  step 'I visit admin settings page' do
-    visit admin_application_settings_path
-  end
-
   step 'I visit spam logs page' do
     visit admin_spam_logs_path
   end
diff --git a/lib/api/api.rb b/lib/api/api.rb
index 67109ceeef98f93638281c66f10344b37edd4f7c..cec2702e44d91fe71f39dee9c3177914c75895c9 100644
--- a/lib/api/api.rb
+++ b/lib/api/api.rb
@@ -64,6 +64,7 @@ module API
     mount ::API::Session
     mount ::API::Settings
     mount ::API::SidekiqMetrics
+    mount ::API::Snippets
     mount ::API::Subscriptions
     mount ::API::SystemHooks
     mount ::API::Tags
diff --git a/lib/api/branches.rb b/lib/api/branches.rb
index 73aed624ea72af930c99d331981d28d5ce76e4ac..0950c3d2e88480c19d92b794c3138b267f4bdd88 100644
--- a/lib/api/branches.rb
+++ b/lib/api/branches.rb
@@ -23,9 +23,9 @@ module API
         success Entities::RepoBranch
       end
       params do
-        requires :branch, type: String, regexp: /.+/, desc: 'The name of the branch'
+        requires :branch, type: String, desc: 'The name of the branch'
       end
-      get ':id/repository/branches/:branch' do
+      get ':id/repository/branches/:branch', requirements: { branch: /.+/ } do
         branch = user_project.repository.find_branch(params[:branch])
         not_found!("Branch") unless branch
 
@@ -39,11 +39,11 @@ module API
         success Entities::RepoBranch
       end
       params do
-        requires :branch, type: String, regexp: /.+/, desc: 'The name of the branch'
+        requires :branch, type: String, desc: 'The name of the branch'
         optional :developers_can_push, type: Boolean, desc: 'Flag if developers can push to that branch'
         optional :developers_can_merge, type: Boolean, desc: 'Flag if developers can merge to that branch'
       end
-      put ':id/repository/branches/:branch/protect' do
+      put ':id/repository/branches/:branch/protect', requirements: { branch: /.+/ } do
         authorize_admin_project
 
         branch = user_project.repository.find_branch(params[:branch])
@@ -76,9 +76,9 @@ module API
         success Entities::RepoBranch
       end
       params do
-        requires :branch, type: String, regexp: /.+/, desc: 'The name of the branch'
+        requires :branch, type: String, desc: 'The name of the branch'
       end
-      put ':id/repository/branches/:branch/unprotect' do
+      put ':id/repository/branches/:branch/unprotect', requirements: { branch: /.+/ } do
         authorize_admin_project
 
         branch = user_project.repository.find_branch(params[:branch])
@@ -112,9 +112,9 @@ module API
 
       desc 'Delete a branch'
       params do
-        requires :branch, type: String, regexp: /.+/, desc: 'The name of the branch'
+        requires :branch, type: String, desc: 'The name of the branch'
       end
-      delete ":id/repository/branches/:branch" do
+      delete ":id/repository/branches/:branch", requirements: { branch: /.+/ } do
         authorize_push_project
 
         result = DeleteBranchService.new(user_project, current_user).
diff --git a/lib/api/commits.rb b/lib/api/commits.rb
index 2670a2d413a4cbb0f3cecdb8dd8569ee334fe797..cf2489dbb6798aac4e79749f4221807341c5e093 100644
--- a/lib/api/commits.rb
+++ b/lib/api/commits.rb
@@ -1,7 +1,6 @@
 require 'mime/types'
 
 module API
-  # Projects commits API
   class Commits < Grape::API
     include PaginationParams
 
@@ -121,6 +120,41 @@ module API
         present paginate(notes), with: Entities::CommitNote
       end
 
+      desc 'Cherry pick commit into a branch' do
+        detail 'This feature was introduced in GitLab 8.15'
+        success Entities::RepoCommit
+      end
+      params do
+        requires :sha, type: String, desc: 'A commit sha to be cherry picked'
+        requires :branch, type: String, desc: 'The name of the branch'
+      end
+      post ':id/repository/commits/:sha/cherry_pick' do
+        authorize! :push_code, user_project
+
+        commit = user_project.commit(params[:sha])
+        not_found!('Commit') unless commit
+
+        branch = user_project.repository.find_branch(params[:branch])
+        not_found!('Branch') unless branch
+
+        commit_params = {
+          commit: commit,
+          create_merge_request: false,
+          source_project: user_project,
+          source_branch: commit.cherry_pick_branch_name,
+          target_branch: params[:branch]
+        }
+
+        result = ::Commits::CherryPickService.new(user_project, current_user, commit_params).execute
+
+        if result[:status] == :success
+          branch = user_project.repository.find_branch(params[:branch])
+          present user_project.repository.commit(branch.dereferenced_target), with: Entities::RepoCommit
+        else
+          render_api_error!(result[:message], 400)
+        end
+      end
+
       desc 'Post comment to commit' do
         success Entities::CommitNote
       end
diff --git a/lib/api/entities.rb b/lib/api/entities.rb
index 006d5f9f44e2c46dd4c346dbbdbed02808953599..01c0f5072ba40dc1b29fc4b5996989e5a9ecdc03 100644
--- a/lib/api/entities.rb
+++ b/lib/api/entities.rb
@@ -201,6 +201,19 @@ module API
       end
     end
 
+    class PersonalSnippet < Grape::Entity
+      expose :id, :title, :file_name
+      expose :author, using: Entities::UserBasic
+      expose :updated_at, :created_at
+
+      expose :web_url do |snippet|
+        Gitlab::UrlBuilder.build(snippet)
+      end
+      expose :raw_url do |snippet|
+        Gitlab::UrlBuilder.build(snippet) + "/raw"
+      end
+    end
+
     class ProjectEntity < Grape::Entity
       expose :id, :iid
       expose(:project_id) { |entity| entity.project.id }
diff --git a/lib/api/groups.rb b/lib/api/groups.rb
index fbf7513302b28eaab5863de145de6dfa5e220c00..9b9d3df74359a97aee4dceda1588b92d0a116077 100644
--- a/lib/api/groups.rb
+++ b/lib/api/groups.rb
@@ -1,7 +1,7 @@
 module API
   class Groups < Grape::API
     include PaginationParams
-    
+
     before { authenticate! }
 
     helpers do
@@ -117,12 +117,24 @@ module API
         success Entities::Project
       end
       params do
+        optional :archived, type: Boolean, default: false, desc: 'Limit by archived status'
+        optional :visibility, type: String, values: %w[public internal private],
+                              desc: 'Limit by visibility'
+        optional :search, type: String, desc: 'Return list of authorized projects matching the search criteria'
+        optional :order_by, type: String, values: %w[id name path created_at updated_at last_activity_at],
+                            default: 'created_at', desc: 'Return projects ordered by field'
+        optional :sort, type: String, values: %w[asc desc], default: 'desc',
+                        desc: 'Return projects sorted in ascending and descending order'
+        optional :simple, type: Boolean, default: false,
+                          desc: 'Return only the ID, URL, name, and path of each project'
         use :pagination
       end
       get ":id/projects" do
         group = find_group!(params[:id])
         projects = GroupProjectsFinder.new(group).execute(current_user)
-        present paginate(projects), with: Entities::Project, user: current_user
+        projects = filter_projects(projects)
+        entity = params[:simple] ? Entities::BasicProjectDetails : Entities::Project
+        present paginate(projects), with: entity, user: current_user
       end
 
       desc 'Transfer a project to the group namespace. Available only for admin.' do
diff --git a/lib/api/helpers.rb b/lib/api/helpers.rb
index 8b0f8deadfa6d7d77104f0302418841cc0993957..746849ef4c01d75bd12adfc9e9554f733154ff62 100644
--- a/lib/api/helpers.rb
+++ b/lib/api/helpers.rb
@@ -7,67 +7,23 @@ module API
     SUDO_HEADER = "HTTP_SUDO"
     SUDO_PARAM = :sudo
 
-    def private_token
-      params[PRIVATE_TOKEN_PARAM] || env[PRIVATE_TOKEN_HEADER]
-    end
-
-    def warden
-      env['warden']
-    end
-
-    # Check the Rails session for valid authentication details
-    #
-    # Until CSRF protection is added to the API, disallow this method for
-    # state-changing endpoints
-    def find_user_from_warden
-      warden.try(:authenticate) if %w[GET HEAD].include?(env['REQUEST_METHOD'])
-    end
-
     def declared_params(options = {})
       options = { include_parent_namespaces: false }.merge(options)
       declared(params, options).to_h.symbolize_keys
     end
 
-    def find_user_by_private_token
-      token = private_token
-      return nil unless token.present?
-
-      User.find_by_authentication_token(token) || User.find_by_personal_access_token(token)
-    end
-
     def current_user
-      @current_user ||= find_user_by_private_token
-      @current_user ||= doorkeeper_guard
-      @current_user ||= find_user_from_warden
-
-      unless @current_user && Gitlab::UserAccess.new(@current_user).allowed?
-        return nil
-      end
+      return @current_user if defined?(@current_user)
 
-      identifier = sudo_identifier
+      @current_user = initial_current_user
 
-      if identifier
-        # We check for private_token because we cannot allow PAT to be used
-        forbidden!('Must be admin to use sudo') unless @current_user.is_admin?
-        forbidden!('Private token must be specified in order to use sudo') unless private_token_used?
-
-        @impersonator = @current_user
-        @current_user = User.by_username_or_id(identifier)
-        not_found!("No user id or username for: #{identifier}") if @current_user.nil?
-      end
+      sudo!
 
       @current_user
     end
 
-    def sudo_identifier
-      identifier ||= params[SUDO_PARAM] || env[SUDO_HEADER]
-
-      # Regex for integers
-      if !!(identifier =~ /\A[0-9]+\z/)
-        identifier.to_i
-      else
-        identifier
-      end
+    def sudo?
+      initial_current_user != current_user
     end
 
     def user_project
@@ -78,6 +34,14 @@ module API
       @available_labels ||= LabelsFinder.new(current_user, project_id: user_project.id).execute
     end
 
+    def find_user(id)
+      if id =~ /^\d+$/
+        User.find_by(id: id)
+      else
+        User.find_by(username: id)
+      end
+    end
+
     def find_project(id)
       if id =~ /^\d+$/
         Project.find_by(id: id)
@@ -96,17 +60,6 @@ module API
       end
     end
 
-    def project_service(project = user_project)
-      @project_service ||= project.find_or_initialize_service(params[:service_slug].underscore)
-      @project_service || not_found!("Service")
-    end
-
-    def service_attributes
-      @service_attributes ||= project_service.fields.inject([]) do |arr, hash|
-        arr << hash[:name].to_sym
-      end
-    end
-
     def find_group(id)
       if id =~ /^\d+$/
         Group.find_by(id: id)
@@ -354,6 +307,69 @@ module API
 
     private
 
+    def private_token
+      params[PRIVATE_TOKEN_PARAM] || env[PRIVATE_TOKEN_HEADER]
+    end
+
+    def warden
+      env['warden']
+    end
+
+    # Check the Rails session for valid authentication details
+    #
+    # Until CSRF protection is added to the API, disallow this method for
+    # state-changing endpoints
+    def find_user_from_warden
+      warden.try(:authenticate) if %w[GET HEAD].include?(env['REQUEST_METHOD'])
+    end
+
+    def find_user_by_private_token
+      token = private_token
+      return nil unless token.present?
+
+      User.find_by_authentication_token(token) || User.find_by_personal_access_token(token)
+    end
+
+    def initial_current_user
+      return @initial_current_user if defined?(@initial_current_user)
+
+      @initial_current_user ||= find_user_by_private_token
+      @initial_current_user ||= doorkeeper_guard
+      @initial_current_user ||= find_user_from_warden
+
+      unless @initial_current_user && Gitlab::UserAccess.new(@initial_current_user).allowed?
+        @initial_current_user = nil
+      end
+
+      @initial_current_user
+    end
+
+    def sudo!
+      return unless sudo_identifier
+      return unless initial_current_user
+
+      unless initial_current_user.is_admin?
+        forbidden!('Must be admin to use sudo')
+      end
+
+      # Only private tokens should be used for the SUDO feature
+      unless private_token == initial_current_user.private_token
+        forbidden!('Private token must be specified in order to use sudo')
+      end
+
+      sudoed_user = find_user(sudo_identifier)
+
+      if sudoed_user
+        @current_user = sudoed_user
+      else
+        not_found!("No user id or username for: #{sudo_identifier}")
+      end
+    end
+
+    def sudo_identifier
+      @sudo_identifier ||= params[SUDO_PARAM] || env[SUDO_HEADER]
+    end
+
     def add_pagination_headers(paginated_data)
       header 'X-Total',       paginated_data.total_count.to_s
       header 'X-Total-Pages', paginated_data.total_pages.to_s
@@ -386,10 +402,6 @@ module API
       links.join(', ')
     end
 
-    def private_token_used?
-      private_token == @current_user.private_token
-    end
-
     def secret_token
       Gitlab::Shell.secret_token
     end
diff --git a/lib/api/merge_requests.rb b/lib/api/merge_requests.rb
index 55bdbc6a47c6514cfbcbd3dab3b2b32040790ddd..5d1fe22f2dff68597fa1bc08850479f2bb3ee2f9 100644
--- a/lib/api/merge_requests.rb
+++ b/lib/api/merge_requests.rb
@@ -143,8 +143,8 @@ module API
           success Entities::MergeRequest
         end
         params do
-          optional :title, type: String, desc: 'The title of the merge request'
-          optional :target_branch, type: String, desc: 'The target branch'
+          optional :title, type: String, allow_blank: false, desc: 'The title of the merge request'
+          optional :target_branch, type: String, allow_blank: false, desc: 'The target branch'
           optional :state_event, type: String, values: %w[close reopen merge],
                                  desc: 'Status of the merge request'
           use :optional_params
diff --git a/lib/api/services.rb b/lib/api/services.rb
index bc427705777fc38ca3856ddfe99dbec23f2736a8..fde2e2746f1bb1cc12fc1982d3de9049e447c5ba 100644
--- a/lib/api/services.rb
+++ b/lib/api/services.rb
@@ -1,84 +1,602 @@
 module API
-  # Projects API
   class Services < Grape::API
+    services = {
+      'asana' => [
+        {
+          required: true,
+          name: :api_key,
+          type: String,
+          desc: 'User API token'
+        },
+        {
+          required: false,
+          name: :restrict_to_branch,
+          type: String,
+          desc: 'Comma-separated list of branches which will be automatically inspected. Leave blank to include all branches'
+        }
+      ],
+      'assembla' => [
+        {
+          required: true,
+          name: :token,
+          type: String,
+          desc: 'The authentication token'
+        },
+        {
+          required: false,
+          name: :subdomain,
+          type: String,
+          desc: 'Subdomain setting'
+        }
+      ],
+      'bamboo' => [
+        {
+          required: true,
+          name: :bamboo_url,
+          type: String,
+          desc: 'Bamboo root URL like https://bamboo.example.com'
+        },
+        {
+          required: true,
+          name: :build_key,
+          type: String,
+          desc: 'Bamboo build plan key like'
+        },
+        {
+          required: true,
+          name: :username,
+          type: String,
+          desc: 'A user with API access, if applicable'
+        },
+        {
+          required: true,
+          name: :password,
+          type: String,
+          desc: 'Passord of the user'
+        }
+      ],
+      'bugzilla' => [
+        {
+          required: true,
+          name: :new_issue_url,
+          type: String,
+          desc: 'New issue URL'
+        },
+        {
+          required: true,
+          name: :issues_url,
+          type: String,
+          desc: 'Issues URL'
+        },
+        {
+          required: true,
+          name: :project_url,
+          type: String,
+          desc: 'Project URL'
+        },
+        {
+          required: false,
+          name: :description,
+          type: String,
+          desc: 'Description'
+        },
+        {
+          required: false,
+          name: :title,
+          type: String,
+          desc: 'Title'
+        }
+      ],
+      'buildkite' => [
+        {
+          required: true,
+          name: :token,
+          type: String,
+          desc: 'Buildkite project GitLab token'
+        },
+        {
+          required: true,
+          name: :project_url,
+          type: String,
+          desc: 'The buildkite project URL'
+        },
+        {
+          required: false,
+          name: :enable_ssl_verification,
+          type: Boolean,
+          desc: 'Enable SSL verification for communication'
+        }
+      ],
+      'builds-email' => [
+        {
+          required: true,
+          name: :recipients,
+          type: String,
+          desc: 'Comma-separated list of recipient email addresses'
+        },
+        {
+          required: false,
+          name: :add_pusher,
+          type: Boolean,
+          desc: 'Add pusher to recipients list'
+        },
+        {
+          required: false,
+          name: :notify_only_broken_builds,
+          type: Boolean,
+          desc: 'Notify only broken builds'
+        }
+      ],
+      'campfire' => [
+        {
+          required: true,
+          name: :token,
+          type: String,
+          desc: 'Campfire token'
+        },
+        {
+          required: false,
+          name: :subdomain,
+          type: String,
+          desc: 'Campfire subdomain'
+        },
+        {
+          required: false,
+          name: :room,
+          type: String,
+          desc: 'Campfire room'
+        },
+      ],
+      'custom-issue-tracker' => [
+        {
+          required: true,
+          name: :new_issue_url,
+          type: String,
+          desc: 'New issue URL'
+        },
+        {
+          required: true,
+          name: :issues_url,
+          type: String,
+          desc: 'Issues URL'
+        },
+        {
+          required: true,
+          name: :project_url,
+          type: String,
+          desc: 'Project URL'
+        },
+        {
+          required: false,
+          name: :description,
+          type: String,
+          desc: 'Description'
+        },
+        {
+          required: false,
+          name: :title,
+          type: String,
+          desc: 'Title'
+        }
+      ],
+      'drone-ci' => [
+        {
+          required: true,
+          name: :token,
+          type: String,
+          desc: 'Drone CI token'
+        },
+        {
+          required: true,
+          name: :drone_url,
+          type: String,
+          desc: 'Drone CI URL'
+        },
+        {
+          required: false,
+          name: :enable_ssl_verification,
+          type: Boolean,
+          desc: 'Enable SSL verification for communication'
+        }
+      ],
+      'emails-on-push' => [
+        {
+          required: true,
+          name: :recipients,
+          type: String,
+          desc: 'Comma-separated list of recipient email addresses'
+        },
+        {
+          required: false,
+          name: :disable_diffs,
+          type: Boolean,
+          desc: 'Disable code diffs'
+        },
+        {
+          required: false,
+          name: :send_from_committer_email,
+          type: Boolean,
+          desc: 'Send from committer'
+        }
+      ],
+      'external-wiki' => [
+        {
+          required: true,
+          name: :external_wiki_url,
+          type: String,
+          desc: 'The URL of the external Wiki'
+        }
+      ],
+      'flowdock' => [
+        {
+          required: true,
+          name: :token,
+          type: String,
+          desc: 'Flowdock token'
+        }
+      ],
+      'gemnasium' => [
+        {
+          required: true,
+          name: :api_key,
+          type: String,
+          desc: 'Your personal API key on gemnasium.com'
+        },
+        {
+          required: true,
+          name: :token,
+          type: String,
+          desc: "The project's slug on gemnasium.com"
+        }
+      ],
+      'hipchat' => [
+        {
+          required: true,
+          name: :token,
+          type: String,
+          desc: 'The room token'
+        },
+        {
+          required: false,
+          name: :room,
+          type: String,
+          desc: 'The room name or ID'
+        },
+        {
+          required: false,
+          name: :color,
+          type: String,
+          desc: 'The room color'
+        },
+        {
+          required: false,
+          name: :notify,
+          type: Boolean,
+          desc: 'Enable notifications'
+        },
+        {
+          required: false,
+          name: :api_version,
+          type: String,
+          desc: 'Leave blank for default (v2)'
+        },
+        {
+          required: false,
+          name: :server,
+          type: String,
+          desc: 'Leave blank for default. https://hipchat.example.com'
+        }
+      ],
+      'irker' => [
+        {
+          required: true,
+          name: :recipients,
+          type: String,
+          desc: 'Recipients/channels separated by whitespaces'
+        },
+        {
+          required: false,
+          name: :default_irc_uri,
+          type: String,
+          desc: 'Default: irc://irc.network.net:6697'
+        },
+        {
+          required: false,
+          name: :server_host,
+          type: String,
+          desc: 'Server host. Default localhost'
+        },
+        {
+          required: false,
+          name: :server_port,
+          type: Integer,
+          desc: 'Server port. Default 6659'
+        },
+        {
+          required: false,
+          name: :colorize_messages,
+          type: Boolean,
+          desc: 'Colorize messages'
+        }
+      ],
+      'jira' => [
+        {
+          required: true,
+          name: :url,
+          type: String,
+          desc: 'The URL to the JIRA project which is being linked to this GitLab project, e.g., https://jira.example.com'
+        },
+        {
+          required: true,
+          name: :project_key,
+          type: String,
+          desc: 'The short identifier for your JIRA project, all uppercase, e.g., PROJ'
+        },
+        {
+          required: false,
+          name: :username,
+          type: String,
+          desc: 'The username of the user created to be used with GitLab/JIRA'
+        },
+        {
+          required: false,
+          name: :password,
+          type: String,
+          desc: 'The password of the user created to be used with GitLab/JIRA'
+        },
+        {
+          required: false,
+          name: :jira_issue_transition_id,
+          type: Integer,
+          desc: 'The ID of a transition that moves issues to a closed state. You can find this number under the JIRA workflow administration (**Administration > Issues > Workflows**) by selecting **View** under **Operations** of the desired workflow of your project. The ID of each state can be found inside the parenthesis of each transition name under the **Transitions (id)** column ([see screenshot][trans]). By default, this ID is set to `2`'
+        }
+      ],
+      'mattermost-slash-commands' => [
+        {
+          required: true,
+          name: :token,
+          type: String,
+          desc: 'The Mattermost token'
+        }
+      ],
+      'pipelines-email' => [
+        {
+          required: true,
+          name: :recipients,
+          type: String,
+          desc: 'Comma-separated list of recipient email addresses'
+        },
+        {
+          required: false,
+          name: :notify_only_broken_builds,
+          type: Boolean,
+          desc: 'Notify only broken builds'
+        }
+      ],
+      'pivotaltracker' => [
+        {
+          required: true,
+          name: :token,
+          type: String,
+          desc: 'The Pivotaltracker token'
+        },
+        {
+          required: false,
+          name: :restrict_to_branch,
+          type: String,
+          desc: 'Comma-separated list of branches which will be automatically inspected. Leave blank to include all branches.'
+        }
+      ],
+      'pushover' => [
+        {
+          required: true,
+          name: :api_key,
+          type: String,
+          desc: 'The application key'
+        },
+        {
+          required: true,
+          name: :user_key,
+          type: String,
+          desc: 'The user key'
+        },
+        {
+          required: true,
+          name: :priority,
+          type: String,
+          desc: 'The priority'
+        },
+        {
+          required: true,
+          name: :device,
+          type: String,
+          desc: 'Leave blank for all active devices'
+        },
+        {
+          required: true,
+          name: :sound,
+          type: String,
+          desc: 'The sound of the notification'
+        }
+      ],
+      'redmine' => [
+        {
+          required: true,
+          name: :new_issue_url,
+          type: String,
+          desc: 'The new issue URL'
+        },
+        {
+          required: true,
+          name: :project_url,
+          type: String,
+          desc: 'The project URL'
+        },
+        {
+          required: true,
+          name: :issues_url,
+          type: String,
+          desc: 'The issues URL'
+        },
+        {
+          required: false,
+          name: :description,
+          type: String,
+          desc: 'The description of the tracker'
+        }
+      ],
+      'slack' => [
+        {
+          required: true,
+          name: :webhook,
+          type: String,
+          desc: 'The Slack webhook. e.g. https://hooks.slack.com/services/...'
+        },
+        {
+          required: false,
+          name: :new_issue_url,
+          type: String,
+          desc: 'The user name'
+        },
+        {
+          required: false,
+          name: :channel,
+          type: String,
+          desc: 'The channel name'
+        }
+      ],
+      'teamcity' => [
+        {
+          required: true,
+          name: :teamcity_url,
+          type: String,
+          desc: 'TeamCity root URL like https://teamcity.example.com'
+        },
+        {
+          required: true,
+          name: :build_type,
+          type: String,
+          desc: 'Build configuration ID'
+        },
+        {
+          required: true,
+          name: :username,
+          type: String,
+          desc: 'A user with permissions to trigger a manual build'
+        },
+        {
+          required: true,
+          name: :password,
+          type: String,
+          desc: 'The password of the user'
+        }
+      ]
+    }.freeze
+
+    trigger_services = {
+      'mattermost-slash-commands' => [
+        {
+          name: :token,
+          type: String,
+          desc: 'The Mattermost token'
+        }
+      ]
+    }.freeze
+
     resource :projects do
       before { authenticate! }
       before { authorize_admin_project }
 
-      # Set <service_slug> service for project
-      #
-      # Example Request:
-      #
-      #   PUT /projects/:id/services/gitlab-ci
-      #
-      put ':id/services/:service_slug' do
-        if project_service
-          validators = project_service.class.validators.select do |s|
-            s.class == ActiveRecord::Validations::PresenceValidator &&
-              s.attributes != [:project_id]
+      helpers do
+        def service_attributes(service)
+          service.fields.inject([]) do |arr, hash|
+            arr << hash[:name].to_sym
           end
+        end
+      end
 
-          required_attributes! validators.map(&:attributes).flatten.uniq
-          attrs = attributes_for_keys service_attributes
+      services.each do |service_slug, settings|
+        desc "Set #{service_slug} service for project"
+        params do
+          settings.each do |setting|
+            if setting[:required]
+              requires setting[:name], type: setting[:type], desc: setting[:desc]
+            else
+              optional setting[:name], type: setting[:type], desc: setting[:desc]
+            end
+          end
+        end
+        put ":id/services/#{service_slug}" do
+          service = user_project.find_or_initialize_service(service_slug.underscore)
+          service_params = declared_params(include_missing: false).merge(active: true)
 
-          if project_service.update_attributes(attrs.merge(active: true))
+          if service.update_attributes(service_params)
             true
           else
-            not_found!
+            render_api_error!('400 Bad Request', 400)
           end
         end
       end
 
-      # Delete <service_slug> service for project
-      #
-      # Example Request:
-      #
-      #   DELETE /project/:id/services/gitlab-ci
-      #
-      delete ':id/services/:service_slug' do
-        if project_service
-          attrs = service_attributes.inject({}) do |hash, key|
-            hash.merge!(key => nil)
-          end
+      desc "Delete a service for project"
+      params do
+        requires :service_slug, type: String, values: services.keys, desc: 'The name of the service'
+      end
+      delete ":id/services/:service_slug" do
+        service = user_project.find_or_initialize_service(params[:service_slug].underscore)
 
-          if project_service.update_attributes(attrs.merge(active: false))
-            true
-          else
-            not_found!
-          end
+        attrs = service_attributes(service).inject({}) do |hash, key|
+          hash.merge!(key => nil)
+        end
+
+        if service.update_attributes(attrs.merge(active: false))
+          true
+        else
+          render_api_error!('400 Bad Request', 400)
         end
       end
 
-      # Get <service_slug> service settings for project
-      #
-      # Example Request:
-      #
-      #   GET /project/:id/services/gitlab-ci
-      #
-      get ':id/services/:service_slug' do
-        present project_service, with: Entities::ProjectService, include_passwords: current_user.is_admin?
+      desc 'Get the service settings for project' do
+        success Entities::ProjectService
+      end
+      params do
+        requires :service_slug, type: String, values: services.keys, desc: 'The name of the service'
+      end
+      get ":id/services/:service_slug" do
+        service = user_project.find_or_initialize_service(params[:service_slug].underscore)
+        present service, with: Entities::ProjectService, include_passwords: current_user.is_admin?
       end
     end
 
-    resource :projects do
-      desc 'Trigger a slash command' do
-        detail 'Added in GitLab 8.13'
+    trigger_services.each do |service_slug, settings|
+      params do
+        requires :id, type: String, desc: 'The ID of a project'
       end
-      post ':id/services/:service_slug/trigger' do
-        project = find_project(params[:id])
+      resource :projects do
+        desc "Trigger a slash command for #{service_slug}" do
+          detail 'Added in GitLab 8.13'
+        end
+        params do
+          settings.each do |setting|
+            requires setting[:name], type: setting[:type], desc: setting[:desc]
+          end
+        end
+        post ":id/services/#{service_slug.underscore}/trigger" do
+          project = find_project(params[:id])
 
-        # This is not accurate, but done to prevent leakage of the project names
-        not_found!('Service') unless project
+          # This is not accurate, but done to prevent leakage of the project names
+          not_found!('Service') unless project
 
-        service = project_service(project)
+          service = project.find_or_initialize_service(service_slug.underscore)
 
-        result = service.try(:active?) && service.try(:trigger, params)
+          result = service.try(:active?) && service.try(:trigger, params)
 
-        if result
-          status result[:status] || 200
-          present result
-        else
-          not_found!('Service')
+          if result
+            status result[:status] || 200
+            present result
+          else
+            not_found!('Service')
+          end
         end
       end
     end
diff --git a/lib/api/snippets.rb b/lib/api/snippets.rb
new file mode 100644
index 0000000000000000000000000000000000000000..e096e6368061f5c8677f6e1543c1e327f7b553a7
--- /dev/null
+++ b/lib/api/snippets.rb
@@ -0,0 +1,137 @@
+module API
+  # Snippets API
+  class Snippets < Grape::API
+    include PaginationParams
+
+    before { authenticate! }
+
+    resource :snippets do
+      helpers do
+        def snippets_for_current_user
+          SnippetsFinder.new.execute(current_user, filter: :by_user, user: current_user)
+        end
+
+        def public_snippets
+          SnippetsFinder.new.execute(current_user, filter: :public)
+        end
+      end
+
+      desc 'Get a snippets list for authenticated user' do
+        detail 'This feature was introduced in GitLab 8.15.'
+        success Entities::PersonalSnippet
+      end
+      params do
+        use :pagination
+      end
+      get do
+        present paginate(snippets_for_current_user), with: Entities::PersonalSnippet
+      end
+
+      desc 'List all public snippets current_user has access to' do
+        detail 'This feature was introduced in GitLab 8.15.'
+        success Entities::PersonalSnippet
+      end
+      params do
+        use :pagination
+      end
+      get 'public' do
+        present paginate(public_snippets), with: Entities::PersonalSnippet
+      end
+
+      desc 'Get a single snippet' do
+        detail 'This feature was introduced in GitLab 8.15.'
+        success Entities::PersonalSnippet
+      end
+      params do
+        requires :id, type: Integer, desc: 'The ID of a snippet'
+      end
+      get ':id' do
+        snippet = snippets_for_current_user.find(params[:id])
+        present snippet, with: Entities::PersonalSnippet
+      end
+
+      desc 'Create new snippet' do
+        detail 'This feature was introduced in GitLab 8.15.'
+        success Entities::PersonalSnippet
+      end
+      params do
+        requires :title, type: String, desc: 'The title of a snippet'
+        requires :file_name, type: String, desc: 'The name of a snippet file'
+        requires :content, type: String, desc: 'The content of a snippet'
+        optional :visibility_level, type: Integer,
+                                    values: Gitlab::VisibilityLevel.values,
+                                    default: Gitlab::VisibilityLevel::INTERNAL,
+                                    desc: 'The visibility level of the snippet'
+      end
+      post do
+        attrs = declared_params(include_missing: false)
+        snippet = CreateSnippetService.new(nil, current_user, attrs).execute
+
+        if snippet.persisted?
+          present snippet, with: Entities::PersonalSnippet
+        else
+          render_validation_error!(snippet)
+        end
+      end
+
+      desc 'Update an existing snippet' do
+        detail 'This feature was introduced in GitLab 8.15.'
+        success Entities::PersonalSnippet
+      end
+      params do
+        requires :id, type: Integer, desc: 'The ID of a snippet'
+        optional :title, type: String, desc: 'The title of a snippet'
+        optional :file_name, type: String, desc: 'The name of a snippet file'
+        optional :content, type: String, desc: 'The content of a snippet'
+        optional :visibility_level, type: Integer,
+                                    values: Gitlab::VisibilityLevel.values,
+                                    desc: 'The visibility level of the snippet'
+        at_least_one_of :title, :file_name, :content, :visibility_level
+      end
+      put ':id' do
+        snippet = snippets_for_current_user.find_by(id: params.delete(:id))
+        return not_found!('Snippet') unless snippet
+        authorize! :update_personal_snippet, snippet
+
+        attrs = declared_params(include_missing: false)
+
+        UpdateSnippetService.new(nil, current_user, snippet, attrs).execute
+        if snippet.persisted?
+          present snippet, with: Entities::PersonalSnippet
+        else
+          render_validation_error!(snippet)
+        end
+      end
+
+      desc 'Remove snippet' do
+        detail 'This feature was introduced in GitLab 8.15.'
+        success Entities::PersonalSnippet
+      end
+      params do
+        requires :id, type: Integer, desc: 'The ID of a snippet'
+      end
+      delete ':id' do
+        snippet = snippets_for_current_user.find_by(id: params.delete(:id))
+        return not_found!('Snippet') unless snippet
+        authorize! :destroy_personal_snippet, snippet
+        snippet.destroy
+        no_content!
+      end
+
+      desc 'Get a raw snippet' do
+        detail 'This feature was introduced in GitLab 8.15.'
+      end
+      params do
+        requires :id, type: Integer, desc: 'The ID of a snippet'
+      end
+      get ":id/raw" do
+        snippet = snippets_for_current_user.find_by(id: params.delete(:id))
+        return not_found!('Snippet') unless snippet
+
+        env['api.format'] = :txt
+        content_type 'text/plain'
+        present snippet.content
+      end
+    end
+  end
+end
diff --git a/lib/api/users.rb b/lib/api/users.rb
index 1dab799dd61e6fc2ba42d11f3b1193d8db7cd7c2..c7db2d7101715d324dcb8ba82c71a32e5a9dd9b0 100644
--- a/lib/api/users.rb
+++ b/lib/api/users.rb
@@ -353,7 +353,7 @@ module API
         success Entities::UserPublic
       end
       get do
-        present current_user, with: @impersonator ? Entities::UserWithPrivateToken : Entities::UserPublic
+        present current_user, with: sudo? ? Entities::UserWithPrivateToken : Entities::UserPublic
       end
 
       desc "Get the currently authenticated user's SSH keys" do
diff --git a/lib/banzai/filter/abstract_reference_filter.rb b/lib/banzai/filter/abstract_reference_filter.rb
index d904a8bd4ae48724ea37128ea3d590a262d62ee9..fd74eeaebe7df719b5a2698dc3536ad45bd8f987 100644
--- a/lib/banzai/filter/abstract_reference_filter.rb
+++ b/lib/banzai/filter/abstract_reference_filter.rb
@@ -248,7 +248,7 @@ module Banzai
       end
 
       def projects_relation_for_paths(paths)
-        Project.where_paths_in(paths).includes(:namespace)
+        Project.where_full_path_in(paths).includes(:namespace)
       end
 
       # Returns projects for the given paths.
diff --git a/lib/banzai/filter/relative_link_filter.rb b/lib/banzai/filter/relative_link_filter.rb
index f09d78be0cee191ca461f0f9b625e79a93da1349..9e23c8f8c553d22dc856fb0a3cba99ca5ad27b52 100644
--- a/lib/banzai/filter/relative_link_filter.rb
+++ b/lib/banzai/filter/relative_link_filter.rb
@@ -46,7 +46,7 @@ module Banzai
       end
 
       def rebuild_relative_uri(uri)
-        file_path = relative_file_path(uri.path)
+        file_path = relative_file_path(uri)
 
         uri.path = [
           relative_url_root,
@@ -59,8 +59,10 @@ module Banzai
         uri
       end
 
-      def relative_file_path(path)
-        nested_path = build_relative_path(path, context[:requested_path])
+      def relative_file_path(uri)
+        path = Addressable::URI.unescape(uri.path)
+        request_path = Addressable::URI.unescape(context[:requested_path])
+        nested_path = build_relative_path(path, request_path)
         file_exists?(nested_path) ? nested_path : path
       end
 
@@ -108,11 +110,7 @@ module Banzai
       end
 
       def uri_type(path)
-        @uri_types[path] ||= begin
-          unescaped_path = Addressable::URI.unescape(path)
-
-          current_commit.uri_type(unescaped_path)
-        end
+        @uri_types[path] ||= current_commit.uri_type(path)
       end
 
       def current_commit
diff --git a/lib/gitlab/allowable.rb b/lib/gitlab/allowable.rb
new file mode 100644
index 0000000000000000000000000000000000000000..f48abcc86d53ac932898619b76c35a361dcffaa6
--- /dev/null
+++ b/lib/gitlab/allowable.rb
@@ -0,0 +1,7 @@
+module Gitlab
+  module Allowable
+    def can?(user, action, subject)
+      Ability.allowed?(user, action, subject)
+    end
+  end
+end
diff --git a/lib/gitlab/ci/status/build/cancelable.rb b/lib/gitlab/ci/status/build/cancelable.rb
new file mode 100644
index 0000000000000000000000000000000000000000..a979fe7d573939eee5fcba65c199ea295b40276e
--- /dev/null
+++ b/lib/gitlab/ci/status/build/cancelable.rb
@@ -0,0 +1,37 @@
+module Gitlab
+  module Ci
+    module Status
+      module Build
+        class Cancelable < SimpleDelegator
+          include Status::Extended
+
+          def has_action?
+            can?(user, :update_build, subject)
+          end
+
+          def action_icon
+            'ban'
+          end
+
+          def action_path
+            cancel_namespace_project_build_path(subject.project.namespace,
+                                                subject.project,
+                                                subject)
+          end
+
+          def action_method
+            :post
+          end
+
+          def action_title
+            'Cancel'
+          end
+
+          def self.matches?(build, user)
+            build.cancelable?
+          end
+        end
+      end
+    end
+  end
+end
diff --git a/lib/gitlab/ci/status/build/common.rb b/lib/gitlab/ci/status/build/common.rb
new file mode 100644
index 0000000000000000000000000000000000000000..3fec2c5d4dbeada7cc9967eb3d387075b8837648
--- /dev/null
+++ b/lib/gitlab/ci/status/build/common.rb
@@ -0,0 +1,19 @@
+module Gitlab
+  module Ci
+    module Status
+      module Build
+        module Common
+          def has_details?
+            can?(user, :read_build, subject)
+          end
+
+          def details_path
+            namespace_project_build_path(subject.project.namespace,
+                                         subject.project,
+                                         subject)
+          end
+        end
+      end
+    end
+  end
+end
diff --git a/lib/gitlab/ci/status/build/factory.rb b/lib/gitlab/ci/status/build/factory.rb
new file mode 100644
index 0000000000000000000000000000000000000000..eee9a64120b287ad29724d425d1432717e75074b
--- /dev/null
+++ b/lib/gitlab/ci/status/build/factory.rb
@@ -0,0 +1,18 @@
+module Gitlab
+  module Ci
+    module Status
+      module Build
+        class Factory < Status::Factory
+          def self.extended_statuses
+            [Status::Build::Stop, Status::Build::Play,
+             Status::Build::Cancelable, Status::Build::Retryable]
+          end
+
+          def self.common_helpers
+            Status::Build::Common
+          end
+        end
+      end
+    end
+  end
+end
diff --git a/lib/gitlab/ci/status/build/play.rb b/lib/gitlab/ci/status/build/play.rb
new file mode 100644
index 0000000000000000000000000000000000000000..5c506e6d59f3a9b36bae18e96588af3f22be66e5
--- /dev/null
+++ b/lib/gitlab/ci/status/build/play.rb
@@ -0,0 +1,53 @@
+module Gitlab
+  module Ci
+    module Status
+      module Build
+        class Play < SimpleDelegator
+          include Status::Extended
+
+          def text
+            'manual'
+          end
+
+          def label
+            'manual play action'
+          end
+
+          def icon
+            'icon_status_manual'
+          end
+
+          def has_action?
+            can?(user, :update_build, subject)
+          end
+
+          def action_icon
+            'play'
+          end
+
+          def action_title
+            'Play'
+          end
+
+          def action_class
+            'ci-play-icon'
+          end
+
+          def action_path
+            play_namespace_project_build_path(subject.project.namespace,
+                                              subject.project,
+                                              subject)
+          end
+
+          def action_method
+            :post
+          end
+
+          def self.matches?(build, user)
+            build.playable? && !build.stops_environment?
+          end
+        end
+      end
+    end
+  end
+end
diff --git a/lib/gitlab/ci/status/build/retryable.rb b/lib/gitlab/ci/status/build/retryable.rb
new file mode 100644
index 0000000000000000000000000000000000000000..8e38d6a8523c19e506fea4537f3db2d011cde202
--- /dev/null
+++ b/lib/gitlab/ci/status/build/retryable.rb
@@ -0,0 +1,37 @@
+module Gitlab
+  module Ci
+    module Status
+      module Build
+        class Retryable < SimpleDelegator
+          include Status::Extended
+
+          def has_action?
+            can?(user, :update_build, subject)
+          end
+
+          def action_icon
+            'refresh'
+          end
+
+          def action_title
+            'Retry'
+          end
+
+          def action_path
+            retry_namespace_project_build_path(subject.project.namespace,
+                                               subject.project,
+                                               subject)
+          end
+
+          def action_method
+            :post
+          end
+
+          def self.matches?(build, user)
+            build.retryable?
+          end
+        end
+      end
+    end
+  end
+end
diff --git a/lib/gitlab/ci/status/build/stop.rb b/lib/gitlab/ci/status/build/stop.rb
new file mode 100644
index 0000000000000000000000000000000000000000..f8ffa95cde47e7884b6a3b8a411018b6b6794183
--- /dev/null
+++ b/lib/gitlab/ci/status/build/stop.rb
@@ -0,0 +1,49 @@
+module Gitlab
+  module Ci
+    module Status
+      module Build
+        class Stop < SimpleDelegator
+          include Status::Extended
+
+          def text
+            'manual'
+          end
+
+          def label
+            'manual stop action'
+          end
+
+          def icon
+            'icon_status_manual'
+          end
+
+          def has_action?
+            can?(user, :update_build, subject)
+          end
+
+          def action_icon
+            'stop'
+          end
+
+          def action_title
+            'Stop'
+          end
+
+          def action_path
+            play_namespace_project_build_path(subject.project.namespace,
+                                              subject.project,
+                                              subject)
+          end
+
+          def action_method
+            :post
+          end
+
+          def self.matches?(build, user)
+            build.playable? && build.stops_environment?
+          end
+        end
+      end
+    end
+  end
+end
diff --git a/lib/gitlab/ci/status/core.rb b/lib/gitlab/ci/status/core.rb
index ce4108fdcf2ff8debdcc01e7b98c81d967d5b0b6..46fef8262c19b3819b8b81158388b9493e7e8f72 100644
--- a/lib/gitlab/ci/status/core.rb
+++ b/lib/gitlab/ci/status/core.rb
@@ -4,10 +4,14 @@ module Gitlab
       # Base abstract class fore core status
       #
       class Core
-        include Gitlab::Routing.url_helpers
+        include Gitlab::Routing
+        include Gitlab::Allowable
 
-        def initialize(subject)
+        attr_reader :subject, :user
+
+        def initialize(subject, user)
           @subject = subject
+          @user = user
         end
 
         def icon
@@ -18,10 +22,6 @@ module Gitlab
           raise NotImplementedError
         end
 
-        def title
-          "#{@subject.class.name.demodulize}: #{label}"
-        end
-
         # Deprecation warning: this method is here because we need to maintain
         # backwards compatibility with legacy statuses. We often do something
         # like "ci-status ci-status-#{status}" to set CSS class.
@@ -34,7 +34,7 @@ module Gitlab
         end
 
         def has_details?
-          raise NotImplementedError
+          false
         end
 
         def details_path
@@ -42,16 +42,27 @@ module Gitlab
         end
 
         def has_action?
-          raise NotImplementedError
+          false
         end
 
         def action_icon
           raise NotImplementedError
         end
 
+        def action_class
+        end
+
         def action_path
           raise NotImplementedError
         end
+
+        def action_method
+          raise NotImplementedError
+        end
+
+        def action_title
+          raise NotImplementedError
+        end
       end
     end
   end
diff --git a/lib/gitlab/ci/status/extended.rb b/lib/gitlab/ci/status/extended.rb
index 6bfb5d38c1f1c1bdfffe322384e6f2da2b9d8b20..d367c9bda69ff2d71f6e6cef91c3ef66fbc7b2b2 100644
--- a/lib/gitlab/ci/status/extended.rb
+++ b/lib/gitlab/ci/status/extended.rb
@@ -2,8 +2,12 @@ module Gitlab
   module Ci
     module Status
       module Extended
-        def matches?(_subject)
-          raise NotImplementedError
+        extend ActiveSupport::Concern
+
+        class_methods do
+          def matches?(_subject, _user)
+            raise NotImplementedError
+          end
         end
       end
     end
diff --git a/lib/gitlab/ci/status/factory.rb b/lib/gitlab/ci/status/factory.rb
index b2f896f2211fc8a597d9edc470c793e806cdd3e8..ae9ef895df49d27e3bb9c81ac02df667fee58e1d 100644
--- a/lib/gitlab/ci/status/factory.rb
+++ b/lib/gitlab/ci/status/factory.rb
@@ -2,10 +2,9 @@ module Gitlab
   module Ci
     module Status
       class Factory
-        attr_reader :subject
-
-        def initialize(subject)
+        def initialize(subject, user)
           @subject = subject
+          @user = user
         end
 
         def fabricate!
@@ -16,27 +15,32 @@ module Gitlab
           end
         end
 
+        def self.extended_statuses
+          []
+        end
+
+        def self.common_helpers
+          Module.new
+        end
+
         private
 
-        def subject_status
-          @subject_status ||= subject.status
+        def simple_status
+          @simple_status ||= @subject.status || :created
         end
 
         def core_status
           Gitlab::Ci::Status
-            .const_get(subject_status.capitalize)
-            .new(subject)
+            .const_get(simple_status.capitalize)
+            .new(@subject, @user)
+            .extend(self.class.common_helpers)
         end
 
         def extended_status
-          @extended ||= extended_statuses.find do |status|
-            status.matches?(subject)
+          @extended ||= self.class.extended_statuses.find do |status|
+            status.matches?(@subject, @user)
           end
         end
-
-        def extended_statuses
-          []
-        end
       end
     end
   end
diff --git a/lib/gitlab/ci/status/pipeline/common.rb b/lib/gitlab/ci/status/pipeline/common.rb
index 25e52bec3da1db99432dd097a4def7d30931d324..76bfd18bf4040f369e53a751d22d8f66d92eb337 100644
--- a/lib/gitlab/ci/status/pipeline/common.rb
+++ b/lib/gitlab/ci/status/pipeline/common.rb
@@ -4,13 +4,13 @@ module Gitlab
       module Pipeline
         module Common
           def has_details?
-            true
+            can?(user, :read_pipeline, subject)
           end
 
           def details_path
-            namespace_project_pipeline_path(@subject.project.namespace,
-                                            @subject.project,
-                                            @subject)
+            namespace_project_pipeline_path(subject.project.namespace,
+                                            subject.project,
+                                            subject)
           end
 
           def has_action?
diff --git a/lib/gitlab/ci/status/pipeline/factory.rb b/lib/gitlab/ci/status/pipeline/factory.rb
index 4ac4ec671d0eb7fdcc84322e2417148c3ff83046..16dcb326be9c46503d0fcd6a19ee6fc1057159b0 100644
--- a/lib/gitlab/ci/status/pipeline/factory.rb
+++ b/lib/gitlab/ci/status/pipeline/factory.rb
@@ -3,14 +3,12 @@ module Gitlab
     module Status
       module Pipeline
         class Factory < Status::Factory
-          private
-
-          def extended_statuses
+          def self.extended_statuses
             [Pipeline::SuccessWithWarnings]
           end
 
-          def core_status
-            super.extend(Status::Pipeline::Common)
+          def self.common_helpers
+            Status::Pipeline::Common
           end
         end
       end
diff --git a/lib/gitlab/ci/status/pipeline/success_with_warnings.rb b/lib/gitlab/ci/status/pipeline/success_with_warnings.rb
index 4b040d60df83320a8e427b04e4736e16cba38821..a7c98f9e909e285dd77dfee4a7358e080797ad92 100644
--- a/lib/gitlab/ci/status/pipeline/success_with_warnings.rb
+++ b/lib/gitlab/ci/status/pipeline/success_with_warnings.rb
@@ -3,7 +3,7 @@ module Gitlab
     module Status
       module Pipeline
         class SuccessWithWarnings < SimpleDelegator
-          extend Status::Extended
+          include Status::Extended
 
           def text
             'passed'
@@ -21,7 +21,7 @@ module Gitlab
             'success_with_warnings'
           end
 
-          def self.matches?(pipeline)
+          def self.matches?(pipeline, user)
             pipeline.success? && pipeline.has_warnings?
           end
         end
diff --git a/lib/gitlab/ci/status/stage/common.rb b/lib/gitlab/ci/status/stage/common.rb
index 14c437d2b98db80214f48dc123acee8ebb41614e..7852f492e1d0738b8f2e33af2e7412dd0c2e8356 100644
--- a/lib/gitlab/ci/status/stage/common.rb
+++ b/lib/gitlab/ci/status/stage/common.rb
@@ -4,14 +4,14 @@ module Gitlab
       module Stage
         module Common
           def has_details?
-            true
+            can?(user, :read_pipeline, subject.pipeline)
           end
 
           def details_path
-            namespace_project_pipeline_path(@subject.project.namespace,
-                                            @subject.project,
-                                            @subject.pipeline,
-                                            anchor: @subject.name)
+            namespace_project_pipeline_path(subject.project.namespace,
+                                            subject.project,
+                                            subject.pipeline,
+                                            anchor: subject.name)
           end
 
           def has_action?
diff --git a/lib/gitlab/ci/status/stage/factory.rb b/lib/gitlab/ci/status/stage/factory.rb
index c6522d5ada154485fab858027db84ece8d453a9c..689a5dd45bc3911e9d323c0fde6c02ec60a4afce 100644
--- a/lib/gitlab/ci/status/stage/factory.rb
+++ b/lib/gitlab/ci/status/stage/factory.rb
@@ -3,10 +3,8 @@ module Gitlab
     module Status
       module Stage
         class Factory < Status::Factory
-          private
-
-          def core_status
-            super.extend(Status::Stage::Common)
+          def self.common_helpers
+            Status::Stage::Common
           end
         end
       end
diff --git a/lib/gitlab/regex.rb b/lib/gitlab/regex.rb
index a06cf6a989c9c50acad542de43b580404ef540e3..d9d1e3cccca858ea5960e998b2f44d7253e020de 100644
--- a/lib/gitlab/regex.rb
+++ b/lib/gitlab/regex.rb
@@ -61,7 +61,7 @@ module Gitlab
     end
 
     def file_name_regex
-      @file_name_regex ||= /\A[a-zA-Z0-9_\-\.\@]*\z/.freeze
+      @file_name_regex ||= /\A[[[:alnum:]]_\-\.\@]*\z/.freeze
     end
 
     def file_name_regex_message
@@ -69,7 +69,7 @@ module Gitlab
     end
 
     def file_path_regex
-      @file_path_regex ||= /\A[a-zA-Z0-9_\-\.\/\@]*\z/.freeze
+      @file_path_regex ||= /\A[[[:alnum:]]_\-\.\/\@]*\z/.freeze
     end
 
     def file_path_regex_message
diff --git a/lib/gitlab/routing.rb b/lib/gitlab/routing.rb
index 5132177de516ee6b9eab38a57adfd87e161169b3..632e2d875000c9fccb8284d49edadd4d8bdbbb2f 100644
--- a/lib/gitlab/routing.rb
+++ b/lib/gitlab/routing.rb
@@ -1,5 +1,11 @@
 module Gitlab
   module Routing
+    extend ActiveSupport::Concern
+
+    included do
+      include Gitlab::Routing.url_helpers
+    end
+
     # Returns the URL helpers Module.
     #
     # This method caches the output as Rails' "url_helpers" method creates an
diff --git a/lib/gitlab/url_builder.rb b/lib/gitlab/url_builder.rb
index 99d0c28e7493911e9c8789b43599a75938b34ca0..ccb456bcc94aa40bb7b2d7b8109174cb6448d9c9 100644
--- a/lib/gitlab/url_builder.rb
+++ b/lib/gitlab/url_builder.rb
@@ -24,6 +24,8 @@ module Gitlab
         wiki_page_url
       when ProjectSnippet
         project_snippet_url(object)
+      when Snippet
+        personal_snippet_url(object)
       else
         raise NotImplementedError.new("No URL builder defined for #{object.class}")
       end
diff --git a/lib/tasks/migrate/setup_postgresql.rake b/lib/tasks/migrate/setup_postgresql.rake
index 141a0b74ec0b4a8151b40fd3320dfccd613f6888..f5caca3ddbfe1fbf2e6fc2513efb53d8e6e54d32 100644
--- a/lib/tasks/migrate/setup_postgresql.rake
+++ b/lib/tasks/migrate/setup_postgresql.rake
@@ -1,8 +1,12 @@
+require Rails.root.join('lib/gitlab/database')
+require Rails.root.join('lib/gitlab/database/migration_helpers')
 require Rails.root.join('db/migrate/20151007120511_namespaces_projects_path_lower_indexes')
 require Rails.root.join('db/migrate/20151008110232_add_users_lower_username_email_indexes')
+require Rails.root.join('db/migrate/20161212142807_add_lower_path_index_to_routes')
 
 desc 'GitLab | Sets up PostgreSQL'
 task setup_postgresql: :environment do
   NamespacesProjectsPathLowerIndexes.new.up
   AddUsersLowerUsernameEmailIndexes.new.up
+  AddLowerPathIndexToRoutes.new.up
 end
diff --git a/package.json b/package.json
index 961989f80127298b1f7c61424c1eb193cdb12172..49b8210e427cec6a3cee3a99a849d1ca79f84571 100644
--- a/package.json
+++ b/package.json
@@ -1,7 +1,7 @@
 {
   "private": true,
   "scripts": {
-    "eslint": "eslint --ext .js,.js.es6 .",
+    "eslint": "eslint --max-warnings 0 --ext .js,.js.es6 .",
     "eslint-fix": "npm run eslint -- --fix",
     "eslint-report": "npm run eslint -- --format html --output-file ./eslint-report.html"
   },
diff --git a/spec/controllers/projects/releases_controller_spec.rb b/spec/controllers/projects/releases_controller_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..9fd5c3b85f627ae4ecc9a9c4e59f1e9b91b3d67e
--- /dev/null
+++ b/spec/controllers/projects/releases_controller_spec.rb
@@ -0,0 +1,55 @@
+require 'spec_helper'
+
+describe Projects::ReleasesController do
+  let!(:project) { create(:project) }
+  let!(:user)    { create(:user) }
+  let!(:release) { create(:release, project: project) }
+  let!(:tag)     { release.tag }
+
+  before do
+    project.team << [user, :developer]
+    sign_in(user)
+  end
+
+  describe 'GET #edit' do
+    it 'initializes a new release' do
+      tag_id = release.tag
+      project.releases.destroy_all
+
+      get :edit, namespace_id: project.namespace.path, project_id: project.path, tag_id: tag_id
+
+      release = assigns(:release)
+      expect(release).not_to be_nil
+      expect(release).not_to be_persisted
+    end
+
+    it 'retrieves an existing release' do
+      get :edit, namespace_id: project.namespace.path, project_id: project.path, tag_id: release.tag
+
+      release = assigns(:release)
+      expect(release).not_to be_nil
+      expect(release).to be_persisted
+    end
+  end
+
+  describe 'PUT #update' do
+    it 'updates release note description' do
+      update_release('description updated')
+
+      release = project.releases.find_by_tag(tag)
+      expect(release.description).to eq("description updated")
+    end
+
+    it 'deletes release note when description is null' do
+      expect { update_release('') }.to change(project.releases, :count).by(-1)
+    end
+  end
+
+  def update_release(description)
+    put :update,
+      namespace_id: project.namespace.to_param,
+      project_id: project.to_param,
+      tag_id: release.tag,
+      release: { description: description }
+  end
+end
diff --git a/spec/factories/ci/builds.rb b/spec/factories/ci/builds.rb
index c443af09075536330e401acce0ebfe9368fdad55..62466c061941a4df5a06a32fb57c567656b7d2a1 100644
--- a/spec/factories/ci/builds.rb
+++ b/spec/factories/ci/builds.rb
@@ -12,12 +12,14 @@ FactoryGirl.define do
     started_at 'Di 29. Okt 09:51:28 CET 2013'
     finished_at 'Di 29. Okt 09:53:28 CET 2013'
     commands 'ls -a'
+
     options do
       {
         image: "ruby:2.1",
         services: ["postgres"]
       }
     end
+
     yaml_variables do
       [
         { key: :DB_NAME, value: 'postgres', public: true }
@@ -60,15 +62,20 @@ FactoryGirl.define do
     end
 
     trait :teardown_environment do
-      options do
-        { environment: { action: 'stop' } }
-      end
+      environment 'staging'
+      options environment: { name: 'staging',
+                             action: 'stop' }
     end
 
     trait :allowed_to_fail do
       allow_failure true
     end
 
+    trait :playable do
+      skipped
+      manual
+    end
+
     after(:build) do |build, evaluator|
       build.project = build.pipeline.project
     end
diff --git a/spec/factories/groups.rb b/spec/factories/groups.rb
index ebd3595ea641b4a26757f131c0f67f9b44abe728..ece6beb9fa961bccaf824e93a974eeb70ffdaf06 100644
--- a/spec/factories/groups.rb
+++ b/spec/factories/groups.rb
@@ -19,5 +19,9 @@ FactoryGirl.define do
     trait :access_requestable do
       request_access_enabled true
     end
+
+    trait :nested do
+      parent factory: :group
+    end
   end
 end
diff --git a/spec/features/admin/admin_groups_spec.rb b/spec/features/admin/admin_groups_spec.rb
index f6d625fa7f6d172806b0f71310da454024d72db5..0aa01fc499a05e1d06df1061b0675b3b630950ab 100644
--- a/spec/features/admin/admin_groups_spec.rb
+++ b/spec/features/admin/admin_groups_spec.rb
@@ -21,7 +21,7 @@ feature 'Admin Groups', feature: true do
     scenario 'shows the visibility level radio populated with the group visibility_level value' do
       group = create(:group, :private)
 
-      visit edit_admin_group_path(group)
+      visit admin_group_edit_path(group)
 
       expect_selected_visibility(group.visibility_level)
     end
diff --git a/spec/features/admin/admin_projects_spec.rb b/spec/features/admin/admin_projects_spec.rb
index 30ded9202a4975f16a42a1b0e8503dc192d72517..a36bfd574cbe5c7e817e565dfb182355278e9543 100644
--- a/spec/features/admin/admin_projects_spec.rb
+++ b/spec/features/admin/admin_projects_spec.rb
@@ -8,11 +8,11 @@ describe "Admin::Projects", feature: true  do
 
   describe "GET /admin/projects" do
     before do
-      visit admin_namespaces_projects_path
+      visit admin_projects_path
     end
 
     it "is ok" do
-      expect(current_path).to eq(admin_namespaces_projects_path)
+      expect(current_path).to eq(admin_projects_path)
     end
 
     it "has projects list" do
@@ -22,7 +22,7 @@ describe "Admin::Projects", feature: true  do
 
   describe "GET /admin/projects/:id" do
     before do
-      visit admin_namespaces_projects_path
+      visit admin_projects_path
       click_link "#{@project.name}"
     end
 
diff --git a/features/steps/admin/settings.rb b/spec/features/admin/admin_settings_spec.rb
similarity index 67%
rename from features/steps/admin/settings.rb
rename to spec/features/admin/admin_settings_spec.rb
index 11dc7f580f03293119157ba1a3cd72744d8907de..8cd66f189bec5e018c92feecd20ac0c7689dc172 100644
--- a/features/steps/admin/settings.rb
+++ b/spec/features/admin/admin_settings_spec.rb
@@ -1,62 +1,53 @@
-class Spinach::Features::AdminSettings < Spinach::FeatureSteps
-  include SharedAuthentication
-  include SharedPaths
-  include SharedAdmin
-  include Gitlab::CurrentSettings
+require 'spec_helper'
 
-  step 'I modify settings and save form' do
+feature 'Admin updates settings', feature: true do
+  before(:each) do
+    login_as :admin
+    visit admin_application_settings_path
+  end
+
+  scenario 'Change application settings' do
     uncheck 'Gravatar enabled'
     fill_in 'Home page URL', with: 'https://about.gitlab.com/'
     fill_in 'Help page text', with: 'Example text'
     click_button 'Save'
-  end
 
-  step 'I should see application settings saved' do
     expect(current_application_settings.gravatar_enabled).to be_falsey
     expect(current_application_settings.home_page_url).to eq "https://about.gitlab.com/"
     expect(page).to have_content "Application settings saved successfully"
   end
 
-  step 'I click on "Service Templates"' do
+  scenario 'Change Slack Service template settings' do
     click_link 'Service Templates'
-  end
-
-  step 'I click on "Slack" service' do
     click_link 'Slack'
-  end
-
-  step 'I check all events and submit form' do
-    page.check('Active')
-    page.check('Push')
-    page.check('Tag push')
-    page.check('Note')
-    page.check('Issue')
-    page.check('Merge request')
-    page.check('Build')
-    page.check('Pipeline')
-    click_on 'Save'
-  end
-
-  step 'I fill out Slack settings' do
     fill_in 'Webhook', with: 'http://localhost'
     fill_in 'Username', with: 'test_user'
     fill_in 'service_push_channel', with: '#test_channel'
     page.check('Notify only broken builds')
-  end
 
-  step 'I should see service template settings saved' do
+    check_all_events
+    click_on 'Save'
+
     expect(page).to have_content 'Application settings saved successfully'
-  end
 
-  step 'I should see all checkboxes checked' do
+    click_link 'Slack'
+
     page.all('input[type=checkbox]').each do |checkbox|
       expect(checkbox).to be_checked
     end
-  end
-
-  step 'I should see Slack settings saved' do
     expect(find_field('Webhook').value).to eq 'http://localhost'
     expect(find_field('Username').value).to eq 'test_user'
     expect(find('#service_push_channel').value).to eq '#test_channel'
   end
+
+  def check_all_events
+    page.check('Active')
+    page.check('Push')
+    page.check('Tag push')
+    page.check('Note')
+    page.check('Issue')
+    page.check('Merge request')
+    page.check('Build')
+    page.check('Pipeline')
+  end
 end
diff --git a/spec/features/dashboard/datetime_on_tooltips_spec.rb b/spec/features/dashboard/datetime_on_tooltips_spec.rb
index 365cb445df1e519d634501a2de2c1c27897bae40..44dfc2dff450b502495603a6877b2d93a7c20bf7 100644
--- a/spec/features/dashboard/datetime_on_tooltips_spec.rb
+++ b/spec/features/dashboard/datetime_on_tooltips_spec.rb
@@ -36,7 +36,7 @@ feature 'Tooltips on .timeago dates', feature: true, js: true do
       visit user_snippets_path(user)
       wait_for_ajax()
 
-      page.find('.js-timeago').hover
+      page.find('.js-timeago.snippet-created-ago').hover
     end
 
     it 'has the datetime formated correctly' do
diff --git a/spec/features/issues/gfm_autocomplete_spec.rb b/spec/features/issues/gfm_autocomplete_spec.rb
index c421da97d76bdc1a3698253f374b45002d386ee7..cd0512a37e66e443b1438ad499b3153412e4fbcb 100644
--- a/spec/features/issues/gfm_autocomplete_spec.rb
+++ b/spec/features/issues/gfm_autocomplete_spec.rb
@@ -2,8 +2,9 @@ require 'rails_helper'
 
 feature 'GFM autocomplete', feature: true, js: true do
   include WaitForAjax
-  let(:user)    { create(:user) }
+  let(:user)    { create(:user, username: 'someone.special') }
   let(:project) { create(:project) }
+  let(:label) { create(:label, project: project, title: 'special+') }
   let(:issue)   { create(:issue, project: project) }
 
   before do
@@ -23,21 +24,69 @@ feature 'GFM autocomplete', feature: true, js: true do
     expect(page).to have_selector('.atwho-container')
   end
 
-  it 'opens autocomplete menu when field is prefixed with non-text character' do
+  it 'doesnt open autocomplete menu character is prefixed with text' do
     page.within '.timeline-content-form' do
-      find('#note_note').native.send_keys('')
+      find('#note_note').native.send_keys('testing')
       find('#note_note').native.send_keys('@')
     end
 
-    expect(page).to have_selector('.atwho-container')
+    expect(page).not_to have_selector('.atwho-view')
   end
 
-  it 'doesnt open autocomplete menu character is prefixed with text' do
-    page.within '.timeline-content-form' do
-      find('#note_note').native.send_keys('testing')
-      find('#note_note').native.send_keys('@')
+  context 'if a selected value has special characters' do
+    it 'wraps the result in double quotes' do
+      note = find('#note_note')
+      page.within '.timeline-content-form' do
+        note.native.send_keys('')
+        note.native.send_keys("~#{label.title[0]}")
+        sleep 1
+        note.click
+      end
+
+      label_item = find('.atwho-view li', text: label.title)
+
+      expect_to_wrap(true, label_item, note, label.title)
     end
 
-    expect(page).not_to have_selector('.atwho-view')
+    it 'doesn\'t wrap for assignee values' do
+      note = find('#note_note')
+      page.within '.timeline-content-form' do
+        note.native.send_keys('')
+        note.native.send_keys("@#{user.username[0]}")
+        sleep 1
+        note.click
+      end
+
+      user_item = find('.atwho-view li', text: user.username)
+
+      expect_to_wrap(false, user_item, note, user.username)
+    end
+
+    it 'doesn\'t wrap for emoji values' do
+      note = find('#note_note')
+      page.within '.timeline-content-form' do
+        note.native.send_keys('')
+        note.native.send_keys(":cartwheel")
+        sleep 1
+        note.click
+      end
+
+      emoji_item = find('.atwho-view li', text: 'cartwheel_tone1')
+
+      expect_to_wrap(false, emoji_item, note, 'cartwheel_tone1')
+    end
+
+    def expect_to_wrap(should_wrap, item, note, value)
+      expect(item).to have_content(value)
+      expect(item).not_to have_content("\"#{value}\"")
+
+      item.click
+
+      if should_wrap
+        expect(note.value).to include("\"#{value}\"")
+      else
+        expect(note.value).not_to include("\"#{value}\"")
+      end
+    end
   end
 end
diff --git a/spec/features/projects/builds_spec.rb b/spec/features/projects/builds_spec.rb
index a0ccc472d11f40c57ca41713777369ac3095110c..8c4d4320dc5c83e9027bd7d7878457b0c5dd22f0 100644
--- a/spec/features/projects/builds_spec.rb
+++ b/spec/features/projects/builds_spec.rb
@@ -227,6 +227,43 @@ feature 'Builds', :feature do
         expect(page).to have_selector('.js-build-value', text: 'TRIGGER_VALUE_1')
       end
     end
+
+    context 'when build starts environment' do
+      let(:environment) { create(:environment, project: project) }
+      let(:pipeline) { create(:ci_pipeline, project: project) }
+
+      context 'build is successfull and has deployment' do
+        let(:deployment) { create(:deployment) }
+        let(:build) { create(:ci_build, :success, environment: environment.name, deployments: [deployment], pipeline: pipeline) }
+
+        it 'shows a link for the build' do
+          visit namespace_project_build_path(project.namespace, project, build)
+
+          expect(page).to have_link environment.name
+        end
+      end
+
+      context 'build is complete and not successfull' do
+        let(:build) { create(:ci_build, :failed, environment: environment.name, pipeline: pipeline) }
+
+        it 'shows a link for the build' do
+          visit namespace_project_build_path(project.namespace, project, build)
+
+          expect(page).to have_link environment.name
+        end
+      end
+
+      context 'build creates a new deployment' do
+        let!(:deployment) { create(:deployment, environment: environment, sha: project.commit.id) }
+        let(:build) { create(:ci_build, :success, environment: environment.name, pipeline: pipeline) }
+
+        it 'shows a link to lastest deployment' do
+          visit namespace_project_build_path(project.namespace, project, build)
+
+          expect(page).to have_link('latest deployment')
+        end
+      end
+    end
   end
 
   describe "POST /:project/builds/:id/cancel" do
diff --git a/spec/features/projects/files/creating_a_file_spec.rb b/spec/features/projects/files/creating_a_file_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..ae4487061307c8a343286ac8a7a7af59c751e973
--- /dev/null
+++ b/spec/features/projects/files/creating_a_file_spec.rb
@@ -0,0 +1,44 @@
+require 'spec_helper'
+
+feature 'User wants to create a file', feature: true do
+  include WaitForAjax
+
+  let(:project) { create(:project) }
+  let(:user) { create(:user) }
+
+  background do
+    project.team << [user, :master]
+    login_as user
+    visit namespace_project_new_blob_path(project.namespace, project, project.default_branch)
+  end
+
+  def submit_new_file(options)
+    file_name = find('#file_name')
+    file_name.set options[:file_name] || 'README.md'
+
+    file_content = find('#file-content')
+    file_content.set options[:file_content] || 'Some content'
+
+    click_button 'Commit Changes'
+  end
+
+  scenario 'file name contains Chinese characters' do
+    submit_new_file(file_name: '测试.md')
+    expect(page).to have_content 'The file has been successfully created.'
+  end
+
+  scenario 'directory name contains Chinese characters' do
+    submit_new_file(file_name: '中文/测试.md')
+    expect(page).to have_content 'The file has been successfully created.'
+  end
+
+  scenario 'file name contains invalid characters' do
+    submit_new_file(file_name: '\\')
+    expect(page).to have_content 'Your changes could not be committed, because the file name can contain only'
+  end
+
+  scenario 'file name contains directory traversal' do
+    submit_new_file(file_name: '../README.md')
+    expect(page).to have_content 'Your changes could not be committed, because the file name cannot include directory traversal.'
+  end
+end
diff --git a/spec/features/security/admin_access_spec.rb b/spec/features/security/admin_access_spec.rb
index fe8cd7b7602380afaecdc79f1ec53123d9584134..e180ca53eb51e8f6c9e438a3a243456e0320573e 100644
--- a/spec/features/security/admin_access_spec.rb
+++ b/spec/features/security/admin_access_spec.rb
@@ -4,7 +4,7 @@ describe "Admin::Projects", feature: true  do
   include AccessMatchers
 
   describe "GET /admin/projects" do
-    subject { admin_namespaces_projects_path }
+    subject { admin_projects_path }
 
     it { is_expected.to be_allowed_for :admin }
     it { is_expected.to be_denied_for :user }
diff --git a/spec/finders/snippets_finder_spec.rb b/spec/finders/snippets_finder_spec.rb
index 28bdc18e8405e23c7ce61f64e5b587f8dd889154..975e99c58072713dec0ebd4a84a9cec9735dd831 100644
--- a/spec/finders/snippets_finder_spec.rb
+++ b/spec/finders/snippets_finder_spec.rb
@@ -9,65 +9,74 @@ describe SnippetsFinder do
   let(:project2) { create(:empty_project, :private, group: group) }
 
   context ':all filter' do
-    before do
-      @snippet1 = create(:personal_snippet, :private)
-      @snippet2 = create(:personal_snippet, :internal)
-      @snippet3 = create(:personal_snippet, :public)
-    end
+    let!(:snippet1) { create(:personal_snippet, :private) }
+    let!(:snippet2) { create(:personal_snippet, :internal) }
+    let!(:snippet3) { create(:personal_snippet, :public) }
 
     it "returns all private and internal snippets" do
       snippets = SnippetsFinder.new.execute(user, filter: :all)
-      expect(snippets).to include(@snippet2, @snippet3)
-      expect(snippets).not_to include(@snippet1)
+      expect(snippets).to include(snippet2, snippet3)
+      expect(snippets).not_to include(snippet1)
     end
 
     it "returns all public snippets" do
       snippets = SnippetsFinder.new.execute(nil, filter: :all)
-      expect(snippets).to include(@snippet3)
-      expect(snippets).not_to include(@snippet1, @snippet2)
+      expect(snippets).to include(snippet3)
+      expect(snippets).not_to include(snippet1, snippet2)
     end
   end
 
-  context ':by_user filter' do
-    before do
-      @snippet1 = create(:personal_snippet, :private,  author: user)
-      @snippet2 = create(:personal_snippet, :internal, author: user)
-      @snippet3 = create(:personal_snippet, :public,   author: user)
+  context ':public filter' do
+    let!(:snippet1) { create(:personal_snippet, :private) }
+    let!(:snippet2) { create(:personal_snippet, :internal) }
+    let!(:snippet3) { create(:personal_snippet, :public) }
+
+    it "returns public public snippets" do
+      snippets = SnippetsFinder.new.execute(nil, filter: :public)
+
+      expect(snippets).to include(snippet3)
+      expect(snippets).not_to include(snippet1, snippet2)
     end
+  end
+
+  context ':by_user filter' do
+    let!(:snippet1) { create(:personal_snippet, :private, author: user) }
+    let!(:snippet2) { create(:personal_snippet, :internal, author: user) }
+    let!(:snippet3) { create(:personal_snippet, :public, author: user) }
 
     it "returns all public and internal snippets" do
       snippets = SnippetsFinder.new.execute(user1, filter: :by_user, user: user)
-      expect(snippets).to include(@snippet2, @snippet3)
-      expect(snippets).not_to include(@snippet1)
+      expect(snippets).to include(snippet2, snippet3)
+      expect(snippets).not_to include(snippet1)
     end
 
     it "returns internal snippets" do
       snippets = SnippetsFinder.new.execute(user, filter: :by_user, user: user, scope: "are_internal")
-      expect(snippets).to include(@snippet2)
-      expect(snippets).not_to include(@snippet1, @snippet3)
+      expect(snippets).to include(snippet2)
+      expect(snippets).not_to include(snippet1, snippet3)
     end
 
     it "returns private snippets" do
       snippets = SnippetsFinder.new.execute(user, filter: :by_user, user: user, scope: "are_private")
-      expect(snippets).to include(@snippet1)
-      expect(snippets).not_to include(@snippet2, @snippet3)
+      expect(snippets).to include(snippet1)
+      expect(snippets).not_to include(snippet2, snippet3)
     end
 
     it "returns public snippets" do
       snippets = SnippetsFinder.new.execute(user, filter: :by_user, user: user, scope: "are_public")
-      expect(snippets).to include(@snippet3)
-      expect(snippets).not_to include(@snippet1, @snippet2)
+      expect(snippets).to include(snippet3)
+      expect(snippets).not_to include(snippet1, snippet2)
     end
 
     it "returns all snippets" do
       snippets = SnippetsFinder.new.execute(user, filter: :by_user, user: user)
-      expect(snippets).to include(@snippet1, @snippet2, @snippet3)
+      expect(snippets).to include(snippet1, snippet2, snippet3)
     end
 
     it "returns only public snippets if unauthenticated user" do
       snippets = SnippetsFinder.new.execute(nil, filter: :by_user, user: user)
-      expect(snippets).to include(@snippet3)
-      expect(snippets).not_to include(@snippet2, @snippet1)
+      expect(snippets).to include(snippet3)
+      expect(snippets).not_to include(snippet2, snippet1)
     end
   end
 
@@ -84,16 +93,39 @@ describe SnippetsFinder do
       expect(snippets).not_to include(@snippet1, @snippet2)
     end
 
-    it "returns public and internal snippets for none project members" do
+    it "returns public and internal snippets for non project members" do
       snippets = SnippetsFinder.new.execute(user, filter: :by_project, project: project1)
       expect(snippets).to include(@snippet2, @snippet3)
       expect(snippets).not_to include(@snippet1)
     end
 
+    it "returns public snippets for non project members" do
+      snippets = SnippetsFinder.new.execute(user, filter: :by_project, project: project1, scope: "are_public")
+      expect(snippets).to include(@snippet3)
+      expect(snippets).not_to include(@snippet1, @snippet2)
+    end
+
+    it "returns internal snippets for non project members" do
+      snippets = SnippetsFinder.new.execute(user, filter: :by_project, project: project1, scope: "are_internal")
+      expect(snippets).to include(@snippet2)
+      expect(snippets).not_to include(@snippet1, @snippet3)
+    end
+
+    it "does not return private snippets for non project members" do
+      snippets = SnippetsFinder.new.execute(user, filter: :by_project, project: project1, scope: "are_private")
+      expect(snippets).not_to include(@snippet1, @snippet2, @snippet3)
+    end
+
     it "returns all snippets for project members" do
       project1.team << [user, :developer]
       snippets = SnippetsFinder.new.execute(user, filter: :by_project, project: project1)
       expect(snippets).to include(@snippet1, @snippet2, @snippet3)
     end
+
+    it "returns private snippets for project members" do
+      project1.team << [user, :developer]
+      snippets = SnippetsFinder.new.execute(user, filter: :by_project, project: project1, scope: "are_private")
+      expect(snippets).to include(@snippet1)
+    end
   end
 end
diff --git a/spec/javascripts/awards_handler_spec.js b/spec/javascripts/awards_handler_spec.js
index ac1404f6e1c627e27b3cc36b87ab7a327547ea27..7b2e3db6218a4a12079475526ffaa5cfd0d0ebc3 100644
--- a/spec/javascripts/awards_handler_spec.js
+++ b/spec/javascripts/awards_handler_spec.js
@@ -33,9 +33,9 @@
   };
 
   describe('AwardsHandler', function() {
-    fixture.preload('awards_handler.html');
+    fixture.preload('issues/open-issue.html.raw');
     beforeEach(function() {
-      fixture.load('awards_handler.html');
+      fixture.load('issues/open-issue.html.raw');
       awardsHandler = new AwardsHandler;
       spyOn(awardsHandler, 'postEmoji').and.callFake((function(_this) {
         return function(url, emoji, cb) {
@@ -113,7 +113,7 @@
     });
     describe('::getAwardUrl', function() {
       return it('should return the url for request', function() {
-        return expect(awardsHandler.getAwardUrl()).toBe('/gitlab-org/gitlab-test/issues/8/toggle_award_emoji');
+        return expect(awardsHandler.getAwardUrl()).toBe('http://test.host/frontend-fixtures/issues-project/issues/1/toggle_award_emoji');
       });
     });
     describe('::addAward and ::checkMutuality', function() {
@@ -209,7 +209,7 @@
         $('.js-add-award').eq(0).click();
         $menu = $('.emoji-menu');
         $block = $('.js-awards-block');
-        $emoji = $menu.find(".emoji-menu-list-item " + selector);
+        $emoji = $menu.find('.emoji-menu-list:not(.frequent-emojis) ' + selector);
         expect($emoji.length).toBe(1);
         expect($block.find(selector).length).toBe(0);
         $emoji.click();
@@ -224,7 +224,7 @@
         openEmojiMenuAndAddEmoji();
         $('.js-add-award').eq(0).click();
         $block = $('.js-awards-block');
-        $emoji = $('.emoji-menu').find(".emoji-menu-list-item " + selector);
+        $emoji = $('.emoji-menu').find(".emoji-menu-list:not(.frequent-emojis) " + selector);
         $emoji.click();
         return expect($block.find(selector).length).toBe(0);
       });
diff --git a/spec/javascripts/fixtures/awards_handler.html.haml b/spec/javascripts/fixtures/awards_handler.html.haml
deleted file mode 100644
index 1ef2e8f862496436a5a60d1cfabd73fd7535093d..0000000000000000000000000000000000000000
--- a/spec/javascripts/fixtures/awards_handler.html.haml
+++ /dev/null
@@ -1,52 +0,0 @@
-.issue-details.issuable-details
-  .detail-page-description.content-block
-    %h2.title Quibusdam sint officiis earum molestiae ipsa autem voluptatem nisi rem.
-    .description.js-task-list-container.is-task-list-enabled
-      .wiki
-        %p Qui exercitationem magnam optio quae fuga earum odio.
-        %textarea.hidden.js-task-list-field Qui exercitationem magnam optio quae fuga earum odio.
-      %small.edited-text
-    .content-block.content-block-small
-      .awards.js-awards-block{"data-award-url" => "/gitlab-org/gitlab-test/issues/8/toggle_award_emoji"}
-        %button.award-control.btn.js-emoji-btn{"data-placement" => "bottom", "data-title" => "", :type => "button"}
-          .icon.emoji-icon.emoji-1F44D{"data-aliases" => "", "data-emoji" => "thumbsup", "data-unicode-name" => "1F44D", :title => "thumbsup"}
-          %span.award-control-text.js-counter 0
-        %button.award-control.btn.js-emoji-btn{"data-placement" => "bottom", "data-title" => "", :type => "button"}
-          .icon.emoji-icon.emoji-1F44E{"data-aliases" => "", "data-emoji" => "thumbsdown", "data-unicode-name" => "1F44E", :title => "thumbsdown"}
-          %span.award-control-text.js-counter 0
-        .award-menu-holder.js-award-holder
-          %button.btn.award-control.js-add-award{:type => "button"}
-            %i.fa.fa-smile-o.award-control-icon.award-control-icon-normal
-            %i.fa.fa-spinner.fa-spin.award-control-icon.award-control-icon-loading
-            %span.award-control-text Add
-    %section.issuable-discussion
-      #notes
-        %ul#notes-list.notes.main-notes-list.timeline
-          %li#note_348.note.note-row-348.timeline-entry{"data-author-id" => "18", "data-editable" => ""}
-            .timeline-entry-inner
-              .timeline-icon
-                %a{:href => "/u/agustin"}
-                  %img.avatar.s40{:alt => "", :src => "#"}/
-              .timeline-content
-                .note-header
-                  %a.author_link{:href => "/u/agustin"}
-                    %span.author Brenna Stokes
-                  .inline.note-headline-light
-                    @agustin commented
-                    %a{:href => "#note_348"}
-                      %time 11 days ago
-                  .note-actions
-                    %span.note-role Reporter
-                    %a.note-action-button.note-emoji-button.js-add-award.js-note-emoji{"data-position" => "right", :href => "#", :title => "Award Emoji"}
-                      %i.fa.fa-spinner.fa-spin
-                      %i.fa.fa-smile-o.link-highlight
-                .js-task-list-container.note-body.is-task-list-enabled
-                  .note-text
-                    %p Suscipit sunt quia quisquam sed eveniet ipsam.
-                  .note-awards
-                    .awards.hidden.js-awards-block{"data-award-url" => "/gitlab-org/gitlab-test/notes/348/toggle_award_emoji"}
-                      .award-menu-holder.js-award-holder
-                        %button.btn.award-control.js-add-award{:type => "button"}
-                          %i.fa.fa-smile-o.award-control-icon.award-control-icon-normal
-                          %i.fa.fa-spinner.fa-spin.award-control-icon.award-control-icon-loading
-                          %span.award-control-text Add
diff --git a/spec/javascripts/fixtures/pipeline_graph.html.haml b/spec/javascripts/fixtures/pipeline_graph.html.haml
new file mode 100644
index 0000000000000000000000000000000000000000..deca50ceaa74b54c1c90755501144b587e23c416
--- /dev/null
+++ b/spec/javascripts/fixtures/pipeline_graph.html.haml
@@ -0,0 +1,15 @@
+%div.pipeline-visualization.js-pipeline-graph
+  %ul.stage-column-list
+    %li.stage-column
+      .stage-name
+        %a{:href => "/"}
+          Test
+          .builds-container
+            %ul
+              %li.build
+                .curve
+                .build-content
+                  %a
+                    %svg
+                    .ci-status-text
+                      stop_review
diff --git a/spec/javascripts/pipelines_spec.js.es6 b/spec/javascripts/pipelines_spec.js.es6
new file mode 100644
index 0000000000000000000000000000000000000000..85c9cf4b4f1d0f3f2c1090b38c3a66d1eaa22a85
--- /dev/null
+++ b/spec/javascripts/pipelines_spec.js.es6
@@ -0,0 +1,25 @@
+//= require pipelines
+
+(() => {
+  describe('Pipelines', () => {
+    fixture.preload('pipeline_graph');
+
+    beforeEach(() => {
+      fixture.load('pipeline_graph');
+    });
+
+    it('should be defined', () => {
+      expect(window.gl.Pipelines).toBeDefined();
+    });
+
+    it('should create a `Pipelines` instance without options', () => {
+      expect(() => { new window.gl.Pipelines(); }).not.toThrow(); //eslint-disable-line
+    });
+
+    it('should create a `Pipelines` instance with options', () => {
+      const pipelines = new window.gl.Pipelines({ foo: 'bar' });
+
+      expect(pipelines.pipelineGraph).toBeDefined();
+    });
+  });
+})();
diff --git a/spec/javascripts/project_title_spec.js b/spec/javascripts/project_title_spec.js
index 49211a6b852603a5ce226c3540b764b23881f4f5..65de1756201f6eaaf56ba426172f8b2ff18ad0f7 100644
--- a/spec/javascripts/project_title_spec.js
+++ b/spec/javascripts/project_title_spec.js
@@ -21,16 +21,18 @@
       return this.project = new Project();
     });
     return describe('project list', function() {
+      var fakeAjaxResponse = function fakeAjaxResponse(req) {
+        var d;
+        expect(req.url).toBe('/api/v3/projects.json?simple=true');
+        d = $.Deferred();
+        d.resolve(this.projects_data);
+        return d.promise();
+      };
+
       beforeEach((function(_this) {
         return function() {
           _this.projects_data = fixture.load('projects.json')[0];
-          return spyOn(jQuery, 'ajax').and.callFake(function(req) {
-            var d;
-            expect(req.url).toBe('/api/v3/projects.json?simple=true');
-            d = $.Deferred();
-            d.resolve(_this.projects_data);
-            return d.promise();
-          });
+          return spyOn(jQuery, 'ajax').and.callFake(fakeAjaxResponse.bind(_this));
         };
       })(this));
       it('to show on toggle click', (function(_this) {
diff --git a/spec/javascripts/smart_interval_spec.js.es6 b/spec/javascripts/smart_interval_spec.js.es6
index ed6166a25a83b40324e62c81596cc2b39594ce81..1b7ca97cde48c6e071e012c5b40a0e76f3465c76 100644
--- a/spec/javascripts/smart_interval_spec.js.es6
+++ b/spec/javascripts/smart_interval_spec.js.es6
@@ -14,8 +14,9 @@
       startingInterval: DEFAULT_STARTING_INTERVAL,
       maxInterval: DEFAULT_MAX_INTERVAL,
       incrementByFactorOf: DEFAULT_INCREMENT_FACTOR,
-      delayStartBy: 0,
       lazyStart: false,
+      immediateExecution: false,
+      hiddenInterval: null,
     };
 
     if (config) {
@@ -114,14 +115,31 @@
           expect(interval.state.intervalId).toBeTruthy();
 
           // simulates triggering of visibilitychange event
-          interval.state.pageVisibility = 'hidden';
-          interval.handleVisibilityChange();
+          interval.handleVisibilityChange({ target: { visibilityState: 'hidden' } });
 
           expect(interval.state.intervalId).toBeUndefined();
           done();
         }, DEFAULT_SHORT_TIMEOUT);
       });
 
+      it('should change to the hidden interval when page is not visible', function (done) {
+        const HIDDEN_INTERVAL = 1500;
+        const interval = createDefaultSmartInterval({ hiddenInterval: HIDDEN_INTERVAL });
+
+        setTimeout(() => {
+          expect(interval.state.intervalId).toBeTruthy();
+          expect(interval.getCurrentInterval() >= DEFAULT_STARTING_INTERVAL &&
+            interval.getCurrentInterval() <= DEFAULT_MAX_INTERVAL).toBeTruthy();
+
+          // simulates triggering of visibilitychange event
+          interval.handleVisibilityChange({ target: { visibilityState: 'hidden' } });
+
+          expect(interval.state.intervalId).toBeTruthy();
+          expect(interval.getCurrentInterval()).toBe(HIDDEN_INTERVAL);
+          done();
+        }, DEFAULT_SHORT_TIMEOUT);
+      });
+
       it('should resume when page is becomes visible at the previous interval', function (done) {
         const interval = this.smartInterval;
 
@@ -129,14 +147,12 @@
           expect(interval.state.intervalId).toBeTruthy();
 
           // simulates triggering of visibilitychange event
-          interval.state.pageVisibility = 'hidden';
-          interval.handleVisibilityChange();
+          interval.handleVisibilityChange({ target: { visibilityState: 'hidden' } });
 
           expect(interval.state.intervalId).toBeUndefined();
 
           // simulates triggering of visibilitychange event
-          interval.state.pageVisibility = 'visible';
-          interval.handleVisibilityChange();
+          interval.handleVisibilityChange({ target: { visibilityState: 'visible' } });
 
           expect(interval.state.intervalId).toBeTruthy();
 
@@ -154,6 +170,11 @@
           done();
         }, DEFAULT_SHORT_TIMEOUT);
       });
+
+      it('should execute callback before first interval', function () {
+        const interval = createDefaultSmartInterval({ immediateExecution: true });
+        expect(interval.cfg.immediateExecution).toBeFalsy();
+      });
     });
   });
 })(window.gl || (window.gl = {}));
diff --git a/spec/lib/banzai/filter/relative_link_filter_spec.rb b/spec/lib/banzai/filter/relative_link_filter_spec.rb
index 2bfa51deb2063406c69d8a1e4c6d45af1231c030..df2dd173b5786d5d8b93e2433c8d8e7bd098ba4c 100644
--- a/spec/lib/banzai/filter/relative_link_filter_spec.rb
+++ b/spec/lib/banzai/filter/relative_link_filter_spec.rb
@@ -175,7 +175,7 @@ describe Banzai::Filter::RelativeLinkFilter, lib: true do
       allow_any_instance_of(described_class).to receive(:uri_type).and_return(:raw)
 
       doc = filter(image(escaped))
-      expect(doc.at_css('img')['src']).to match '/raw/'
+      expect(doc.at_css('img')['src']).to eq "/#{project_path}/raw/#{Addressable::URI.escape(ref)}/#{escaped}"
     end
 
     context 'when requested path is a file in the repo' do
diff --git a/spec/lib/gitlab/allowable_spec.rb b/spec/lib/gitlab/allowable_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..87733d53e92b7f45953d2c2a2f3cacc13a86209c
--- /dev/null
+++ b/spec/lib/gitlab/allowable_spec.rb
@@ -0,0 +1,27 @@
+require 'spec_helper'
+
+describe Gitlab::Allowable do
+  subject do
+    Class.new.include(described_class).new
+  end
+
+  describe '#can?' do
+    let(:user) { create(:user) }
+
+    context 'when user is allowed to do something' do
+      let(:project) { create(:empty_project, :public) }
+
+      it 'reports correct ability to perform action' do
+        expect(subject.can?(user, :read_project, project)).to be true
+      end
+    end
+
+    context 'when user is not allowed to do something' do
+      let(:project) { create(:empty_project, :private) }
+
+      it 'reports correct ability to perform action' do
+        expect(subject.can?(user, :read_project, project)).to be false
+      end
+    end
+  end
+end
diff --git a/spec/lib/gitlab/ci/status/build/cancelable_spec.rb b/spec/lib/gitlab/ci/status/build/cancelable_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..9376bce17a13f0a5355bdb67f0a87745f705b4ad
--- /dev/null
+++ b/spec/lib/gitlab/ci/status/build/cancelable_spec.rb
@@ -0,0 +1,86 @@
+require 'spec_helper'
+
+describe Gitlab::Ci::Status::Build::Cancelable do
+  let(:status) { double('core status') }
+  let(:user) { double('user') }
+
+  subject do
+    described_class.new(status)
+  end
+
+  describe '#text' do
+    it 'does not override status text' do
+      expect(status).to receive(:text)
+
+      subject.text
+    end
+  end
+
+  describe '#icon' do
+    it 'does not override status icon' do
+      expect(status).to receive(:icon)
+
+      subject.icon
+    end
+  end
+
+  describe '#label' do
+    it 'does not override status label' do
+      expect(status).to receive(:label)
+
+      subject.label
+    end
+  end
+
+  describe 'action details' do
+    let(:user) { create(:user) }
+    let(:build) { create(:ci_build) }
+    let(:status) { Gitlab::Ci::Status::Core.new(build, user) }
+
+    describe '#has_action?' do
+      context 'when user is allowed to update build' do
+        before { build.project.team << [user, :developer] }
+
+        it { is_expected.to have_action }
+      end
+
+      context 'when user is not allowed to update build' do
+        it { is_expected.not_to have_action }
+      end
+    end
+
+    describe '#action_path' do
+      it { expect(subject.action_path).to include "#{build.id}/cancel" }
+    end
+
+    describe '#action_icon' do
+      it { expect(subject.action_icon).to eq 'ban' }
+    end
+
+    describe '#action_title' do
+      it { expect(subject.action_title).to eq 'Cancel' }
+    end
+  end
+
+  describe '.matches?' do
+    subject { described_class.matches?(build, user) }
+
+    context 'when build is cancelable' do
+      let(:build) do
+        create(:ci_build, :running)
+      end
+
+      it 'is a correct match' do
+        expect(subject).to be true
+      end
+    end
+
+    context 'when build is not cancelable' do
+      let(:build) { create(:ci_build, :success) }
+
+      it 'does not match' do
+        expect(subject).to be false
+      end
+    end
+  end
+end
diff --git a/spec/lib/gitlab/ci/status/build/common_spec.rb b/spec/lib/gitlab/ci/status/build/common_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..40b96b1807b096a553dd5028ba30da93d11d9e35
--- /dev/null
+++ b/spec/lib/gitlab/ci/status/build/common_spec.rb
@@ -0,0 +1,37 @@
+require 'spec_helper'
+
+describe Gitlab::Ci::Status::Build::Common do
+  let(:user) { create(:user) }
+  let(:build) { create(:ci_build) }
+  let(:project) { build.project }
+
+  subject do
+    Gitlab::Ci::Status::Core
+      .new(build, user)
+      .extend(described_class)
+  end
+
+  describe '#has_action?' do
+    it { is_expected.not_to have_action }
+  end
+
+  describe '#has_details?' do
+    context 'when user has access to read build' do
+      before { project.team << [user, :developer] }
+
+      it { is_expected.to have_details }
+    end
+
+    context 'when user does not have access to read build' do
+      before { project.update(public_builds: false) }
+
+      it { is_expected.not_to have_details }
+    end
+  end
+
+  describe '#details_path' do
+    it 'links to the build details page' do
+      expect(subject.details_path).to include "builds/#{build.id}"
+    end
+  end
+end
diff --git a/spec/lib/gitlab/ci/status/build/factory_spec.rb b/spec/lib/gitlab/ci/status/build/factory_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..dccb29b5ef606b380137e2b9090bf616bbfe4afe
--- /dev/null
+++ b/spec/lib/gitlab/ci/status/build/factory_spec.rb
@@ -0,0 +1,141 @@
+require 'spec_helper'
+
+describe Gitlab::Ci::Status::Build::Factory do
+  let(:user) { create(:user) }
+  let(:project) { build.project }
+
+  subject { described_class.new(build, user) }
+  let(:status) { subject.fabricate! }
+
+  before { project.team << [user, :developer] }
+
+  context 'when build is successful' do
+    let(:build) { create(:ci_build, :success) }
+
+    it 'fabricates a retryable build status' do
+      expect(status).to be_a Gitlab::Ci::Status::Build::Retryable
+    end
+
+    it 'fabricates status with correct details' do
+      expect(status.text).to eq 'passed'
+      expect(status.icon).to eq 'icon_status_success'
+      expect(status.label).to eq 'passed'
+      expect(status).to have_details
+      expect(status).to have_action
+    end
+  end
+
+  context 'when build is failed' do
+    let(:build) { create(:ci_build, :failed) }
+
+    it 'fabricates a retryable build status' do
+      expect(status).to be_a Gitlab::Ci::Status::Build::Retryable
+    end
+
+    it 'fabricates status with correct details' do
+      expect(status.text).to eq 'failed'
+      expect(status.icon).to eq 'icon_status_failed'
+      expect(status.label).to eq 'failed'
+      expect(status).to have_details
+      expect(status).to have_action
+    end
+  end
+
+  context 'when build is a canceled' do
+    let(:build) { create(:ci_build, :canceled) }
+
+    it 'fabricates a retryable build status' do
+      expect(status).to be_a Gitlab::Ci::Status::Build::Retryable
+    end
+
+    it 'fabricates status with correct details' do
+      expect(status.text).to eq 'canceled'
+      expect(status.icon).to eq 'icon_status_canceled'
+      expect(status.label).to eq 'canceled'
+      expect(status).to have_details
+      expect(status).to have_action
+    end
+  end
+
+  context 'when build is running' do
+    let(:build) { create(:ci_build, :running) }
+
+    it 'fabricates a canceable build status' do
+      expect(status).to be_a Gitlab::Ci::Status::Build::Cancelable
+    end
+
+    it 'fabricates status with correct details' do
+      expect(status.text).to eq 'running'
+      expect(status.icon).to eq 'icon_status_running'
+      expect(status.label).to eq 'running'
+      expect(status).to have_details
+      expect(status).to have_action
+    end
+  end
+
+  context 'when build is pending' do
+    let(:build) { create(:ci_build, :pending) }
+
+    it 'fabricates a cancelable build status' do
+      expect(status).to be_a Gitlab::Ci::Status::Build::Cancelable
+    end
+
+    it 'fabricates status with correct details' do
+      expect(status.text).to eq 'pending'
+      expect(status.icon).to eq 'icon_status_pending'
+      expect(status.label).to eq 'pending'
+      expect(status).to have_details
+      expect(status).to have_action
+    end
+  end
+
+  context 'when build is skipped' do
+    let(:build) { create(:ci_build, :skipped) }
+
+    it 'fabricates a core skipped status' do
+      expect(status).to be_a Gitlab::Ci::Status::Skipped
+    end
+
+    it 'fabricates status with correct details' do
+      expect(status.text).to eq 'skipped'
+      expect(status.icon).to eq 'icon_status_skipped'
+      expect(status.label).to eq 'skipped'
+      expect(status).to have_details
+      expect(status).not_to have_action
+    end
+  end
+
+  context 'when build is a manual action' do
+    context 'when build is a play action' do
+      let(:build) { create(:ci_build, :playable) }
+
+      it 'fabricates a core skipped status' do
+        expect(status).to be_a Gitlab::Ci::Status::Build::Play
+      end
+
+      it 'fabricates status with correct details' do
+        expect(status.text).to eq 'manual'
+        expect(status.icon).to eq 'icon_status_manual'
+        expect(status.label).to eq 'manual play action'
+        expect(status).to have_details
+        expect(status).to have_action
+      end
+    end
+
+    context 'when build is an environment stop action' do
+      let(:build) { create(:ci_build, :playable, :teardown_environment) }
+
+      it 'fabricates a core skipped status' do
+        expect(status).to be_a Gitlab::Ci::Status::Build::Stop
+      end
+
+      it 'fabricates status with correct details' do
+        expect(status.text).to eq 'manual'
+        expect(status.icon).to eq 'icon_status_manual'
+        expect(status.label).to eq 'manual stop action'
+        expect(status).to have_details
+        expect(status).to have_action
+      end
+    end
+  end
+end
diff --git a/spec/lib/gitlab/ci/status/build/play_spec.rb b/spec/lib/gitlab/ci/status/build/play_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..4ddf04a8e11b21cc0c1dd0e5fef71220fca87926
--- /dev/null
+++ b/spec/lib/gitlab/ci/status/build/play_spec.rb
@@ -0,0 +1,82 @@
+require 'spec_helper'
+
+describe Gitlab::Ci::Status::Build::Play do
+  let(:status) { double('core') }
+  let(:user) { double('user') }
+
+  subject { described_class.new(status) }
+
+  describe '#text' do
+    it { expect(subject.text).to eq 'manual' }
+  end
+
+  describe '#label' do
+    it { expect(subject.label).to eq 'manual play action' }
+  end
+
+  describe '#icon' do
+    it { expect(subject.icon).to eq 'icon_status_manual' }
+  end
+
+  describe 'action details' do
+    let(:user) { create(:user) }
+    let(:build) { create(:ci_build) }
+    let(:status) { Gitlab::Ci::Status::Core.new(build, user) }
+
+    describe '#has_action?' do
+      context 'when user is allowed to update build' do
+        before { build.project.team << [user, :developer] }
+
+        it { is_expected.to have_action }
+      end
+
+      context 'when user is not allowed to update build' do
+        it { is_expected.not_to have_action }
+      end
+    end
+
+    describe '#action_path' do
+      it { expect(subject.action_path).to include "#{build.id}/play" }
+    end
+
+    describe '#action_icon' do
+      it { expect(subject.action_icon).to eq 'play' }
+    end
+
+    describe '#action_title' do
+      it { expect(subject.action_title).to eq 'Play' }
+    end
+  end
+
+  describe '.matches?' do
+    subject { described_class.matches?(build, user) }
+
+    context 'when build is playable' do
+      context 'when build stops an environment' do
+        let(:build) do
+          create(:ci_build, :playable, :teardown_environment)
+        end
+
+        it 'does not match' do
+          expect(subject).to be false
+        end
+      end
+
+      context 'when build does not stop an environment' do
+        let(:build) { create(:ci_build, :playable) }
+
+        it 'is a correct match' do
+          expect(subject).to be true
+        end
+      end
+    end
+
+    context 'when build is not playable' do
+      let(:build) { create(:ci_build) }
+
+      it 'does not match' do
+        expect(subject).to be false
+      end
+    end
+  end
+end
diff --git a/spec/lib/gitlab/ci/status/build/retryable_spec.rb b/spec/lib/gitlab/ci/status/build/retryable_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..d61e5bbaa6bddfe2bc53c95a8eff2bd4a857bfcd
--- /dev/null
+++ b/spec/lib/gitlab/ci/status/build/retryable_spec.rb
@@ -0,0 +1,86 @@
+require 'spec_helper'
+
+describe Gitlab::Ci::Status::Build::Retryable do
+  let(:status) { double('core status') }
+  let(:user) { double('user') }
+
+  subject do
+    described_class.new(status)
+  end
+
+  describe '#text' do
+    it 'does not override status text' do
+      expect(status).to receive(:text)
+
+      subject.text
+    end
+  end
+
+  describe '#icon' do
+    it 'does not override status icon' do
+      expect(status).to receive(:icon)
+
+      subject.icon
+    end
+  end
+
+  describe '#label' do
+    it 'does not override status label' do
+      expect(status).to receive(:label)
+
+      subject.label
+    end
+  end
+
+  describe 'action details' do
+    let(:user) { create(:user) }
+    let(:build) { create(:ci_build) }
+    let(:status) { Gitlab::Ci::Status::Core.new(build, user) }
+
+    describe '#has_action?' do
+      context 'when user is allowed to update build' do
+        before { build.project.team << [user, :developer] }
+
+        it { is_expected.to have_action }
+      end
+
+      context 'when user is not allowed to update build' do
+        it { is_expected.not_to have_action }
+      end
+    end
+
+    describe '#action_path' do
+      it { expect(subject.action_path).to include "#{build.id}/retry" }
+    end
+
+    describe '#action_icon' do
+      it { expect(subject.action_icon).to eq 'refresh' }
+    end
+
+    describe '#action_title' do
+      it { expect(subject.action_title).to eq 'Retry' }
+    end
+  end
+
+  describe '.matches?' do
+    subject { described_class.matches?(build, user) }
+
+    context 'when build is retryable' do
+      let(:build) do
+        create(:ci_build, :success)
+      end
+
+      it 'is a correct match' do
+        expect(subject).to be true
+      end
+    end
+
+    context 'when build is not retryable' do
+      let(:build) { create(:ci_build, :running) }
+
+      it 'does not match' do
+        expect(subject).to be false
+      end
+    end
+  end
+end
diff --git a/spec/lib/gitlab/ci/status/build/stop_spec.rb b/spec/lib/gitlab/ci/status/build/stop_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..59a85b55f906379eea7574148080b7b51c105d54
--- /dev/null
+++ b/spec/lib/gitlab/ci/status/build/stop_spec.rb
@@ -0,0 +1,84 @@
+require 'spec_helper'
+
+describe Gitlab::Ci::Status::Build::Stop do
+  let(:status) { double('core status') }
+  let(:user) { double('user') }
+
+  subject do
+    described_class.new(status)
+  end
+
+  describe '#text' do
+    it { expect(subject.text).to eq 'manual' }
+  end
+
+  describe '#label' do
+    it { expect(subject.label).to eq 'manual stop action' }
+  end
+
+  describe '#icon' do
+    it { expect(subject.icon).to eq 'icon_status_manual' }
+  end
+
+  describe 'action details' do
+    let(:user) { create(:user) }
+    let(:build) { create(:ci_build) }
+    let(:status) { Gitlab::Ci::Status::Core.new(build, user) }
+
+    describe '#has_action?' do
+      context 'when user is allowed to update build' do
+        before { build.project.team << [user, :developer] }
+
+        it { is_expected.to have_action }
+      end
+
+      context 'when user is not allowed to update build' do
+        it { is_expected.not_to have_action }
+      end
+    end
+
+    describe '#action_path' do
+      it { expect(subject.action_path).to include "#{build.id}/play" }
+    end
+
+    describe '#action_icon' do
+      it { expect(subject.action_icon).to eq 'stop' }
+    end
+
+    describe '#action_title' do
+      it { expect(subject.action_title).to eq 'Stop' }
+    end
+  end
+
+  describe '.matches?' do
+    subject { described_class.matches?(build, user) }
+
+    context 'when build is playable' do
+      context 'when build stops an environment' do
+        let(:build) do
+          create(:ci_build, :playable, :teardown_environment)
+        end
+
+        it 'is a correct match' do
+          expect(subject).to be true
+        end
+      end
+
+      context 'when build does not stop an environment' do
+        let(:build) { create(:ci_build, :playable) }
+
+        it 'does not match' do
+          expect(subject).to be false
+        end
+      end
+    end
+
+    context 'when build is not playable' do
+      let(:build) { create(:ci_build) }
+
+      it 'does not match' do
+        expect(subject).to be false
+      end
+    end
+  end
+end
diff --git a/spec/lib/gitlab/ci/status/canceled_spec.rb b/spec/lib/gitlab/ci/status/canceled_spec.rb
index 619ecbcba6737475dd5336859ee4c515baf1bf9f..4639278ad450f8d08bb4797e2b29cc1ace421a34 100644
--- a/spec/lib/gitlab/ci/status/canceled_spec.rb
+++ b/spec/lib/gitlab/ci/status/canceled_spec.rb
@@ -1,7 +1,9 @@
 require 'spec_helper'
 
 describe Gitlab::Ci::Status::Canceled do
-  subject { described_class.new(double('subject')) }
+  subject do
+    described_class.new(double('subject'), double('user'))
+  end
 
   describe '#text' do
     it { expect(subject.label).to eq 'canceled' }
@@ -14,8 +16,4 @@ describe Gitlab::Ci::Status::Canceled do
   describe '#icon' do
     it { expect(subject.icon).to eq 'icon_status_canceled' }
   end
-
-  describe '#title' do
-    it { expect(subject.title).to eq 'Double: canceled' }
-  end
 end
diff --git a/spec/lib/gitlab/ci/status/created_spec.rb b/spec/lib/gitlab/ci/status/created_spec.rb
index 157302c65a845c248fa593038050cfcdf113100f..2ce176a29d634cf5df60379045e801b272a17fa1 100644
--- a/spec/lib/gitlab/ci/status/created_spec.rb
+++ b/spec/lib/gitlab/ci/status/created_spec.rb
@@ -1,7 +1,9 @@
 require 'spec_helper'
 
 describe Gitlab::Ci::Status::Created do
-  subject { described_class.new(double('subject')) }
+  subject do
+    described_class.new(double('subject'), double('user'))
+  end
 
   describe '#text' do
     it { expect(subject.label).to eq 'created' }
@@ -14,8 +16,4 @@ describe Gitlab::Ci::Status::Created do
   describe '#icon' do
     it { expect(subject.icon).to eq 'icon_status_created' }
   end
-
-  describe '#title' do
-    it { expect(subject.title).to eq 'Double: created' }
-  end
 end
diff --git a/spec/lib/gitlab/ci/status/extended_spec.rb b/spec/lib/gitlab/ci/status/extended_spec.rb
index 120e121aae5f31849f1b92e1e52992a04ab3b502..c2d74ca5cde0cbad5026e10fd590829d02b33c9d 100644
--- a/spec/lib/gitlab/ci/status/extended_spec.rb
+++ b/spec/lib/gitlab/ci/status/extended_spec.rb
@@ -2,11 +2,11 @@ require 'spec_helper'
 
 describe Gitlab::Ci::Status::Extended do
   subject do
-    Class.new.extend(described_class)
+    Class.new.include(described_class)
   end
 
   it 'requires subclass to implement matcher' do
-    expect { subject.matches?(double) }
+    expect { subject.matches?(double, double) }
       .to raise_error(NotImplementedError)
   end
 end
diff --git a/spec/lib/gitlab/ci/status/factory_spec.rb b/spec/lib/gitlab/ci/status/factory_spec.rb
index d5bd7f7102ba959fb9506572a2caf660650c2031..f92a1c149bf1c604c47fe2f81b917b57d649c395 100644
--- a/spec/lib/gitlab/ci/status/factory_spec.rb
+++ b/spec/lib/gitlab/ci/status/factory_spec.rb
@@ -2,15 +2,17 @@ require 'spec_helper'
 
 describe Gitlab::Ci::Status::Factory do
   subject do
-    described_class.new(object)
+    described_class.new(resource, user)
   end
 
+  let(:user) { create(:user) }
+
   let(:status) { subject.fabricate! }
 
   context 'when object has a core status' do
     HasStatus::AVAILABLE_STATUSES.each do |core_status|
       context "when core status is #{core_status}" do
-        let(:object) { double(status: core_status) }
+        let(:resource) { double(status: core_status) }
 
         it "fabricates a core status #{core_status}" do
           expect(status).to be_a(
diff --git a/spec/lib/gitlab/ci/status/failed_spec.rb b/spec/lib/gitlab/ci/status/failed_spec.rb
index 0b3cb8168e6eb1c49d83dc481b62bb73268f0c22..9d527e6a7efe50e1e5db8819c381fe6493c72adb 100644
--- a/spec/lib/gitlab/ci/status/failed_spec.rb
+++ b/spec/lib/gitlab/ci/status/failed_spec.rb
@@ -1,7 +1,9 @@
 require 'spec_helper'
 
 describe Gitlab::Ci::Status::Failed do
-  subject { described_class.new(double('subject')) }
+  subject do
+    described_class.new(double('subject'), double('user'))
+  end
 
   describe '#text' do
     it { expect(subject.label).to eq 'failed' }
@@ -14,8 +16,4 @@ describe Gitlab::Ci::Status::Failed do
   describe '#icon' do
     it { expect(subject.icon).to eq 'icon_status_failed' }
   end
-
-  describe '#title' do
-    it { expect(subject.title).to eq 'Double: failed' }
-  end
 end
diff --git a/spec/lib/gitlab/ci/status/pending_spec.rb b/spec/lib/gitlab/ci/status/pending_spec.rb
index 57c901c1202fa3bfb506633813ec654b86c8d148..d03f595d3c75c37792efa0e8e7116293c82082dc 100644
--- a/spec/lib/gitlab/ci/status/pending_spec.rb
+++ b/spec/lib/gitlab/ci/status/pending_spec.rb
@@ -1,7 +1,9 @@
 require 'spec_helper'
 
 describe Gitlab::Ci::Status::Pending do
-  subject { described_class.new(double('subject')) }
+  subject do
+    described_class.new(double('subject'), double('user'))
+  end
 
   describe '#text' do
     it { expect(subject.label).to eq 'pending' }
@@ -14,8 +16,4 @@ describe Gitlab::Ci::Status::Pending do
   describe '#icon' do
     it { expect(subject.icon).to eq 'icon_status_pending' }
   end
-
-  describe '#title' do
-    it { expect(subject.title).to eq 'Double: pending' }
-  end
 end
diff --git a/spec/lib/gitlab/ci/status/pipeline/common_spec.rb b/spec/lib/gitlab/ci/status/pipeline/common_spec.rb
index 21adee3f8e7d462a5d579ed628239bf4d010c380..d665674bf70095e8ff8ae2726c3b33a198dee2ab 100644
--- a/spec/lib/gitlab/ci/status/pipeline/common_spec.rb
+++ b/spec/lib/gitlab/ci/status/pipeline/common_spec.rb
@@ -1,23 +1,36 @@
 require 'spec_helper'
 
 describe Gitlab::Ci::Status::Pipeline::Common do
-  let(:pipeline) { create(:ci_pipeline) }
+  let(:user) { create(:user) }
+  let(:project) { create(:empty_project, :private) }
+  let(:pipeline) { create(:ci_pipeline, project: project) }
 
   subject do
-    Class.new(Gitlab::Ci::Status::Core)
-      .new(pipeline).extend(described_class)
+    Gitlab::Ci::Status::Core
+      .new(pipeline, user)
+      .extend(described_class)
   end
 
-  it 'does not have action' do
-    expect(subject).not_to have_action
+  describe '#has_action?' do
+    it { is_expected.not_to have_action }
   end
 
-  it 'has details' do
-    expect(subject).to have_details
+  describe '#has_details?' do
+    context 'when user has access to read pipeline' do
+      before { project.team << [user, :developer] }
+
+      it { is_expected.to have_details }
+    end
+
+    context 'when user does not have access to read pipeline' do
+      it { is_expected.not_to have_details }
+    end
   end
 
-  it 'links to the pipeline details page' do
-    expect(subject.details_path)
-      .to include "pipelines/#{pipeline.id}"
+  describe '#details_path' do
+    it 'links to the pipeline details page' do
+      expect(subject.details_path)
+        .to include "pipelines/#{pipeline.id}"
+    end
   end
 end
diff --git a/spec/lib/gitlab/ci/status/pipeline/factory_spec.rb b/spec/lib/gitlab/ci/status/pipeline/factory_spec.rb
index d6243940f2e5eaf37bc389c268d3b6aba3db811d..d4a2dc7fcc1e1bb0d5213ffb099dc12cd8a61f44 100644
--- a/spec/lib/gitlab/ci/status/pipeline/factory_spec.rb
+++ b/spec/lib/gitlab/ci/status/pipeline/factory_spec.rb
@@ -1,14 +1,21 @@
 require 'spec_helper'
 
 describe Gitlab::Ci::Status::Pipeline::Factory do
+  let(:user) { create(:user) }
+  let(:project) { pipeline.project }
+
   subject do
-    described_class.new(pipeline)
+    described_class.new(pipeline, user)
   end
 
   let(:status) do
     subject.fabricate!
   end
 
+  before do
+    project.team << [user, :developer]
+  end
+
   context 'when pipeline has a core status' do
     HasStatus::AVAILABLE_STATUSES.each do |core_status|
       context "when core status is #{core_status}" do
diff --git a/spec/lib/gitlab/ci/status/pipeline/success_with_warnings_spec.rb b/spec/lib/gitlab/ci/status/pipeline/success_with_warnings_spec.rb
index 02e526e3de2001aa6dccb43454dc2eda24b6e892..7e3383c307f1f8057e3edd6db7fb2939ffae8ea5 100644
--- a/spec/lib/gitlab/ci/status/pipeline/success_with_warnings_spec.rb
+++ b/spec/lib/gitlab/ci/status/pipeline/success_with_warnings_spec.rb
@@ -29,13 +29,13 @@ describe Gitlab::Ci::Status::Pipeline::SuccessWithWarnings do
         end
 
         it 'is a correct match' do
-          expect(described_class.matches?(pipeline)).to eq true
+          expect(described_class.matches?(pipeline, double)).to eq true
         end
       end
 
       context 'when pipeline does not have warnings' do
         it 'does not match' do
-          expect(described_class.matches?(pipeline)).to eq false
+          expect(described_class.matches?(pipeline, double)).to eq false
         end
       end
     end
@@ -51,13 +51,13 @@ describe Gitlab::Ci::Status::Pipeline::SuccessWithWarnings do
         end
 
         it 'does not match' do
-          expect(described_class.matches?(pipeline)).to eq false
+          expect(described_class.matches?(pipeline, double)).to eq false
         end
       end
 
       context 'when pipeline does not have warnings' do
         it 'does not match' do
-          expect(described_class.matches?(pipeline)).to eq false
+          expect(described_class.matches?(pipeline, double)).to eq false
         end
       end
     end
diff --git a/spec/lib/gitlab/ci/status/running_spec.rb b/spec/lib/gitlab/ci/status/running_spec.rb
index c023f1872cca14b647dd30017649d1a175466b30..9f47090d396af1afb84a8a2b0e029ede6cc5ae6a 100644
--- a/spec/lib/gitlab/ci/status/running_spec.rb
+++ b/spec/lib/gitlab/ci/status/running_spec.rb
@@ -1,7 +1,9 @@
 require 'spec_helper'
 
 describe Gitlab::Ci::Status::Running do
-  subject { described_class.new(double('subject')) }
+  subject do
+    described_class.new(double('subject'), double('user'))
+  end
 
   describe '#text' do
     it { expect(subject.label).to eq 'running' }
@@ -14,8 +16,4 @@ describe Gitlab::Ci::Status::Running do
   describe '#icon' do
     it { expect(subject.icon).to eq 'icon_status_running' }
   end
-
-  describe '#title' do
-    it { expect(subject.title).to eq 'Double: running' }
-  end
 end
diff --git a/spec/lib/gitlab/ci/status/skipped_spec.rb b/spec/lib/gitlab/ci/status/skipped_spec.rb
index d4f7f4b3b707fec1f0bbf9fe9957199b0adf60b8..94601648a8de25b762c7cf8e53c6410d273e40b8 100644
--- a/spec/lib/gitlab/ci/status/skipped_spec.rb
+++ b/spec/lib/gitlab/ci/status/skipped_spec.rb
@@ -1,7 +1,9 @@
 require 'spec_helper'
 
 describe Gitlab::Ci::Status::Skipped do
-  subject { described_class.new(double('subject')) }
+  subject do
+    described_class.new(double('subject'), double('user'))
+  end
 
   describe '#text' do
     it { expect(subject.label).to eq 'skipped' }
@@ -14,8 +16,4 @@ describe Gitlab::Ci::Status::Skipped do
   describe '#icon' do
     it { expect(subject.icon).to eq 'icon_status_skipped' }
   end
-
-  describe '#title' do
-    it { expect(subject.title).to eq 'Double: skipped' }
-  end
 end
diff --git a/spec/lib/gitlab/ci/status/stage/common_spec.rb b/spec/lib/gitlab/ci/status/stage/common_spec.rb
index f3259c6f23e0738cbba6c14f70a179065b1f0dd5..8814a7614a0715ff95da507f92cc38fee506f0fb 100644
--- a/spec/lib/gitlab/ci/status/stage/common_spec.rb
+++ b/spec/lib/gitlab/ci/status/stage/common_spec.rb
@@ -1,26 +1,43 @@
 require 'spec_helper'
 
 describe Gitlab::Ci::Status::Stage::Common do
-  let(:pipeline) { create(:ci_empty_pipeline) }
-  let(:stage) { build(:ci_stage, pipeline: pipeline, name: 'test') }
+  let(:user) { create(:user) }
+  let(:project) { create(:empty_project) }
+  let(:pipeline) { create(:ci_empty_pipeline, project: project) }
+
+  let(:stage) do
+    build(:ci_stage, pipeline: pipeline, name: 'test')
+  end
 
   subject do
     Class.new(Gitlab::Ci::Status::Core)
-      .new(stage).extend(described_class)
+      .new(stage, user).extend(described_class)
   end
 
   it 'does not have action' do
     expect(subject).not_to have_action
   end
 
-  it 'has details' do
-    expect(subject).to have_details
-  end
-
   it 'links to the pipeline details page' do
     expect(subject.details_path)
       .to include "pipelines/#{pipeline.id}"
     expect(subject.details_path)
       .to include "##{stage.name}"
   end
+
+  context 'when user has permission to read pipeline' do
+    before do
+      project.team << [user, :master]
+    end
+
+    it 'has details' do
+      expect(subject).to have_details
+    end
+  end
+
+  context 'when user does not have permission to read pipeline' do
+    it 'does not have details' do
+      expect(subject).not_to have_details
+    end
+  end
 end
diff --git a/spec/lib/gitlab/ci/status/stage/factory_spec.rb b/spec/lib/gitlab/ci/status/stage/factory_spec.rb
index 17929665c836f11f924ba4dc906df0d04a1697c0..6f8721d30c22c14de01d88ea1095397a6643de73 100644
--- a/spec/lib/gitlab/ci/status/stage/factory_spec.rb
+++ b/spec/lib/gitlab/ci/status/stage/factory_spec.rb
@@ -1,17 +1,26 @@
 require 'spec_helper'
 
 describe Gitlab::Ci::Status::Stage::Factory do
-  let(:pipeline) { create(:ci_empty_pipeline) }
-  let(:stage) { build(:ci_stage, pipeline: pipeline, name: 'test') }
+  let(:user) { create(:user) }
+  let(:project) { create(:empty_project) }
+  let(:pipeline) { create(:ci_empty_pipeline, project: project) }
+
+  let(:stage) do
+    build(:ci_stage, pipeline: pipeline, name: 'test')
+  end
 
   subject do
-    described_class.new(stage)
+    described_class.new(stage, user)
   end
 
   let(:status) do
     subject.fabricate!
   end
 
+  before do
+    project.team << [user, :developer]
+  end
+
   context 'when stage has a core status' do
     HasStatus::AVAILABLE_STATUSES.each do |core_status|
       context "when core status is #{core_status}" do
diff --git a/spec/lib/gitlab/ci/status/success_spec.rb b/spec/lib/gitlab/ci/status/success_spec.rb
index 9e261a3aa5f207ac63acb708610093449f991524..90f9f615e0d52afe10d51df48c4b8d5c691ad37d 100644
--- a/spec/lib/gitlab/ci/status/success_spec.rb
+++ b/spec/lib/gitlab/ci/status/success_spec.rb
@@ -1,7 +1,9 @@
 require 'spec_helper'
 
 describe Gitlab::Ci::Status::Success do
-  subject { described_class.new(double('subject')) }
+  subject do
+    described_class.new(double('subject'), double('user'))
+  end
 
   describe '#text' do
     it { expect(subject.label).to eq 'passed' }
@@ -14,8 +16,4 @@ describe Gitlab::Ci::Status::Success do
   describe '#icon' do
     it { expect(subject.icon).to eq 'icon_status_success' }
   end
-
-  describe '#title' do
-    it { expect(subject.title).to eq 'Double: passed' }
-  end
 end
diff --git a/spec/lib/gitlab/routing_spec.rb b/spec/lib/gitlab/routing_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..01d5acfc15b7ef244be147ba51fab4bfc92e2045
--- /dev/null
+++ b/spec/lib/gitlab/routing_spec.rb
@@ -0,0 +1,23 @@
+require 'spec_helper'
+
+describe Gitlab::Routing do
+  context 'when module is included' do
+    subject do
+      Class.new.include(described_class).new
+    end
+
+    it 'makes it possible to access url helpers' do
+      expect(subject).to respond_to(:namespace_project_path)
+    end
+  end
+
+  context 'when module is not included' do
+    subject do
+      Class.new.include(described_class.url_helpers).new
+    end
+
+    it 'exposes url helpers module through a method' do
+      expect(subject).to respond_to(:namespace_project_path)
+    end
+  end
+end
diff --git a/spec/migrations/migrate_process_commit_worker_jobs_spec.rb b/spec/migrations/migrate_process_commit_worker_jobs_spec.rb
index 52428547a9f451a671ea4e0f6220c681fc6c8926..6a93deb5412e54591fbae25d71418a2c2d05fea6 100644
--- a/spec/migrations/migrate_process_commit_worker_jobs_spec.rb
+++ b/spec/migrations/migrate_process_commit_worker_jobs_spec.rb
@@ -1,3 +1,5 @@
+# encoding: utf-8
+
 require 'spec_helper'
 require Rails.root.join('db', 'migrate', '20161124141322_migrate_process_commit_worker_jobs.rb')
 
@@ -59,6 +61,10 @@ describe MigrateProcessCommitWorkerJobs do
       Sidekiq.redis { |r| r.llen('queue:process_commit') }
     end
 
+    def pop_job
+      JSON.load(Sidekiq.redis { |r| r.lpop('queue:process_commit') })
+    end
+
     before do
       Sidekiq.redis do |redis|
         job = JSON.dump(args: [project.id, user.id, commit.oid])
@@ -92,11 +98,28 @@ describe MigrateProcessCommitWorkerJobs do
       expect(job_count).to eq(1)
     end
 
+    it 'encodes data to UTF-8' do
+      allow_any_instance_of(Rugged::Repository).to receive(:lookup).
+        with(commit.oid).
+        and_return(commit)
+
+      allow(commit).to receive(:message).
+        and_return('김치'.force_encoding('BINARY'))
+
+      migration.up
+
+      job = pop_job
+
+      # We don't care so much about what is being stored, instead we just want
+      # to make sure the encoding is right so that JSON encoding the data
+      # doesn't produce any errors.
+      expect(job['args'][2]['message'].encoding).to eq(Encoding::UTF_8)
+    end
+
     context 'a migrated job' do
       let(:job) do
         migration.up
-
-        JSON.load(Sidekiq.redis { |r| r.lpop('queue:process_commit') })
+        pop_job
       end
 
       let(:commit_hash) do
diff --git a/spec/models/build_spec.rb b/spec/models/build_spec.rb
index d4970e38f7ca1ce6ddff1ae5d5dc17d22935734a..7f39aff7639ec9bdd30df93b8e858f161588d344 100644
--- a/spec/models/build_spec.rb
+++ b/spec/models/build_spec.rb
@@ -899,21 +899,87 @@ describe Ci::Build, models: true do
     end
   end
 
+  describe '#cancelable?' do
+    subject { build }
+
+    context 'when build is cancelable' do
+      context 'when build is pending' do
+        it { is_expected.to be_cancelable }
+      end
+
+      context 'when build is running' do
+        before do
+          build.run!
+        end
+
+        it { is_expected.to be_cancelable }
+      end
+    end
+
+    context 'when build is not cancelable' do
+      context 'when build is successful' do
+        before do
+          build.success!
+        end
+
+        it { is_expected.not_to be_cancelable }
+      end
+
+      context 'when build is failed' do
+        before do
+          build.drop!
+        end
+
+        it { is_expected.not_to be_cancelable }
+      end
+    end
+  end
+
   describe '#retryable?' do
-    context 'when build is running' do
-      before do
-        build.run!
+    subject { build }
+
+    context 'when build is retryable' do
+      context 'when build is successful' do
+        before do
+          build.success!
+        end
+
+        it { is_expected.to be_retryable }
+      end
+
+      context 'when build is failed' do
+        before do
+          build.drop!
+        end
+
+        it { is_expected.to be_retryable }
       end
 
-      it { expect(build).not_to be_retryable }
+      context 'when build is canceled' do
+        before do
+          build.cancel!
+        end
+
+        it { is_expected.to be_retryable }
+      end
     end
 
-    context 'when build is finished' do
-      before do
-        build.success!
+    context 'when build is not retryable' do
+      context 'when build is running' do
+        before do
+          build.run!
+        end
+
+        it { is_expected.not_to be_retryable }
       end
 
-      it { expect(build).to be_retryable }
+      context 'when build is skipped' do
+        before do
+          build.skip!
+        end
+
+        it { is_expected.not_to be_retryable }
+      end
     end
   end
 
@@ -1180,4 +1246,13 @@ describe Ci::Build, models: true do
       it { is_expected.to eq('review/master') }
     end
   end
+
+  describe '#detailed_status' do
+    let(:user) { create(:user) }
+
+    it 'returns a detailed status' do
+      expect(build.detailed_status(user))
+        .to be_a Gitlab::Ci::Status::Build::Cancelable
+    end
+  end
 end
diff --git a/spec/models/ci/pipeline_spec.rb b/spec/models/ci/pipeline_spec.rb
index 8158e71dd55aa92572efbafc75f0e77fe53701fa..e78ae14b7377497795d8f01aeb6def1cec2424d4 100644
--- a/spec/models/ci/pipeline_spec.rb
+++ b/spec/models/ci/pipeline_spec.rb
@@ -442,11 +442,15 @@ describe Ci::Pipeline, models: true do
   end
 
   describe '#detailed_status' do
+    let(:user) { create(:user) }
+
+    subject { pipeline.detailed_status(user) }
+
     context 'when pipeline is created' do
       let(:pipeline) { create(:ci_pipeline, status: :created) }
 
       it 'returns detailed status for created pipeline' do
-        expect(pipeline.detailed_status.text).to eq 'created'
+        expect(subject.text).to eq 'created'
       end
     end
 
@@ -454,7 +458,7 @@ describe Ci::Pipeline, models: true do
       let(:pipeline) { create(:ci_pipeline, status: :pending) }
 
       it 'returns detailed status for pending pipeline' do
-        expect(pipeline.detailed_status.text).to eq 'pending'
+        expect(subject.text).to eq 'pending'
       end
     end
 
@@ -462,7 +466,7 @@ describe Ci::Pipeline, models: true do
       let(:pipeline) { create(:ci_pipeline, status: :running) }
 
       it 'returns detailed status for running pipeline' do
-        expect(pipeline.detailed_status.text).to eq 'running'
+        expect(subject.text).to eq 'running'
       end
     end
 
@@ -470,7 +474,7 @@ describe Ci::Pipeline, models: true do
       let(:pipeline) { create(:ci_pipeline, status: :success) }
 
       it 'returns detailed status for successful pipeline' do
-        expect(pipeline.detailed_status.text).to eq 'passed'
+        expect(subject.text).to eq 'passed'
       end
     end
 
@@ -478,7 +482,7 @@ describe Ci::Pipeline, models: true do
       let(:pipeline) { create(:ci_pipeline, status: :failed) }
 
       it 'returns detailed status for failed pipeline' do
-        expect(pipeline.detailed_status.text).to eq 'failed'
+        expect(subject.text).to eq 'failed'
       end
     end
 
@@ -486,7 +490,7 @@ describe Ci::Pipeline, models: true do
       let(:pipeline) { create(:ci_pipeline, status: :canceled) }
 
       it 'returns detailed status for canceled pipeline' do
-        expect(pipeline.detailed_status.text).to eq 'canceled'
+        expect(subject.text).to eq 'canceled'
       end
     end
 
@@ -494,7 +498,7 @@ describe Ci::Pipeline, models: true do
       let(:pipeline) { create(:ci_pipeline, status: :skipped) }
 
       it 'returns detailed status for skipped pipeline' do
-        expect(pipeline.detailed_status.text).to eq 'skipped'
+        expect(subject.text).to eq 'skipped'
       end
     end
 
@@ -506,7 +510,7 @@ describe Ci::Pipeline, models: true do
       end
 
       it 'retruns detailed status for successful pipeline with warnings' do
-        expect(pipeline.detailed_status.label).to eq 'passed with warnings'
+        expect(subject.label).to eq 'passed with warnings'
       end
     end
   end
diff --git a/spec/models/ci/stage_spec.rb b/spec/models/ci/stage_spec.rb
index f232761dba282b636ff4f8c75d900a9c02cb00f7..8fff38f7cda1b6ac5d994d5b56ba0b35657851d6 100644
--- a/spec/models/ci/stage_spec.rb
+++ b/spec/models/ci/stage_spec.rb
@@ -68,7 +68,9 @@ describe Ci::Stage, models: true do
   end
 
   describe '#detailed_status' do
-    subject { stage.detailed_status }
+    let(:user) { create(:user) }
+
+    subject { stage.detailed_status(user) }
 
     context 'when build is created' do
       let!(:stage_build) { create_job(:ci_build, status: :created) }
diff --git a/spec/models/commit_status_spec.rb b/spec/models/commit_status_spec.rb
index 1ec08c2a9d0ef72b3a01bec979ebb512945dffdf..701f3323c0f5bc4468d8d7def7039d9a69fa2787 100644
--- a/spec/models/commit_status_spec.rb
+++ b/spec/models/commit_status_spec.rb
@@ -234,4 +234,13 @@ describe CommitStatus, models: true do
       end
     end
   end
+
+  describe '#detailed_status' do
+    let(:user) { create(:user) }
+
+    it 'returns a detailed status' do
+      expect(commit_status.detailed_status(user))
+        .to be_a Gitlab::Ci::Status::Success
+    end
+  end
 end
diff --git a/spec/models/concerns/routable_spec.rb b/spec/models/concerns/routable_spec.rb
index 0acefc0c1d5f627ad4acf8a7acd8a8724707e498..b556135532f3b725e21d3531ff71a157a1b19e75 100644
--- a/spec/models/concerns/routable_spec.rb
+++ b/spec/models/concerns/routable_spec.rb
@@ -3,6 +3,10 @@ require 'spec_helper'
 describe Group, 'Routable' do
   let!(:group) { create(:group) }
 
+  describe 'Validations' do
+    it { is_expected.to validate_presence_of(:route) }
+  end
+
   describe 'Associations' do
     it { is_expected.to have_one(:route).dependent(:destroy) }
   end
@@ -35,16 +39,16 @@ describe Group, 'Routable' do
     it { expect(described_class.find_by_full_path('unknown')).to eq(nil) }
   end
 
-  describe '.where_paths_in' do
+  describe '.where_full_path_in' do
     context 'without any paths' do
       it 'returns an empty relation' do
-        expect(described_class.where_paths_in([])).to eq([])
+        expect(described_class.where_full_path_in([])).to eq([])
       end
     end
 
     context 'without any valid paths' do
       it 'returns an empty relation' do
-        expect(described_class.where_paths_in(%w[unknown])).to eq([])
+        expect(described_class.where_full_path_in(%w[unknown])).to eq([])
       end
     end
 
@@ -52,13 +56,13 @@ describe Group, 'Routable' do
       let!(:nested_group) { create(:group, parent: group) }
 
       it 'returns the projects matching the paths' do
-        result = described_class.where_paths_in([group.to_param, nested_group.to_param])
+        result = described_class.where_full_path_in([group.to_param, nested_group.to_param])
 
         expect(result).to contain_exactly(group, nested_group)
       end
 
       it 'returns projects regardless of the casing of paths' do
-        result = described_class.where_paths_in([group.to_param.upcase, nested_group.to_param.upcase])
+        result = described_class.where_full_path_in([group.to_param.upcase, nested_group.to_param.upcase])
 
         expect(result).to contain_exactly(group, nested_group)
       end
diff --git a/spec/models/generic_commit_status_spec.rb b/spec/models/generic_commit_status_spec.rb
index 615cfe3142b1b78dc8adc9c178d635a03cc61b09..6004bfdb7b747960da08681e0705fce223bb2282 100644
--- a/spec/models/generic_commit_status_spec.rb
+++ b/spec/models/generic_commit_status_spec.rb
@@ -1,8 +1,11 @@
 require 'spec_helper'
 
 describe GenericCommitStatus, models: true do
-  let(:pipeline) { FactoryGirl.create :ci_pipeline }
-  let(:generic_commit_status) { FactoryGirl.create :generic_commit_status, pipeline: pipeline }
+  let(:pipeline) { create(:ci_pipeline) }
+
+  let(:generic_commit_status) do
+    create(:generic_commit_status, pipeline: pipeline)
+  end
 
   describe '#context' do
     subject { generic_commit_status.context }
@@ -17,6 +20,15 @@ describe GenericCommitStatus, models: true do
     it { is_expected.to eq([:external]) }
   end
 
+  describe '#detailed_status' do
+    let(:user) { create(:user) }
+
+    it 'returns detailed status object' do
+      expect(generic_commit_status.detailed_status(user))
+        .to be_a Gitlab::Ci::Status::Success
+    end
+  end
+
   describe 'set_default_values' do
     before do
       generic_commit_status.context = nil
diff --git a/spec/models/group_spec.rb b/spec/models/group_spec.rb
index 40f1cf92e0074ae2a0ce7c86025b0879d30582e0..893c6827a91379b0aa91c8e27d405a722da5d220 100644
--- a/spec/models/group_spec.rb
+++ b/spec/models/group_spec.rb
@@ -270,4 +270,11 @@ describe Group, models: true do
       expect(group.web_url).to include("groups/#{group.name}")
     end
   end
+
+  describe 'nested group' do
+    subject { create(:group, :nested) }
+
+    it { is_expected.to be_valid }
+    it { expect(subject.parent).to be_kind_of(Group) }
+  end
 end
diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb
index bad6ed9e146f963724912c5670a0919f0ffab5d1..8b20ee816140831c2dbd75bf4793412073722643 100644
--- a/spec/models/user_spec.rb
+++ b/spec/models/user_spec.rb
@@ -727,17 +727,6 @@ describe User, models: true do
     end
   end
 
-  describe 'by_username_or_id' do
-    let(:user1) { create(:user, username: 'foo') }
-
-    it "gets the correct user" do
-      expect(User.by_username_or_id(user1.id)).to eq(user1)
-      expect(User.by_username_or_id('foo')).to eq(user1)
-      expect(User.by_username_or_id(-1)).to be_nil
-      expect(User.by_username_or_id('bar')).to be_nil
-    end
-  end
-
   describe '.find_by_ssh_key_id' do
     context 'using an existing SSH key ID' do
       let(:user) { create(:user) }
diff --git a/spec/requests/api/branches_spec.rb b/spec/requests/api/branches_spec.rb
index 55b8c8c0c69b6c01c5763378b65a188e53a45de8..2878e0cb59b1a4e28c874843e14ba0c41104ed00 100644
--- a/spec/requests/api/branches_spec.rb
+++ b/spec/requests/api/branches_spec.rb
@@ -11,6 +11,7 @@ describe API::Branches, api: true  do
   let!(:guest) { create(:project_member, :guest, user: user2, project: project) }
   let!(:branch_name) { 'feature' }
   let!(:branch_sha) { '0b4bc9a49b562e85de7cc9e834518ea6828729b9' }
+  let!(:branch_with_dot) { CreateBranchService.new(project, user).execute("with.1.2.3", "master") }
 
   describe "GET /projects/:id/repository/branches" do
     it "returns an array of project branches" do
@@ -37,6 +38,13 @@ describe API::Branches, api: true  do
       expect(json_response['developers_can_merge']).to eq(false)
     end
 
+    it "returns the branch information for a single branch with dots in the name" do
+      get api("/projects/#{project.id}/repository/branches/with.1.2.3", user)
+
+      expect(response).to have_http_status(200)
+      expect(json_response['name']).to eq("with.1.2.3")
+    end
+
     context 'on a merged branch' do
       it "returns the branch information for a single branch" do
         get api("/projects/#{project.id}/repository/branches/merge-test", user)
@@ -71,6 +79,14 @@ describe API::Branches, api: true  do
         expect(json_response['developers_can_merge']).to eq(false)
       end
 
+      it "protects a single branch with dots in the name" do
+        put api("/projects/#{project.id}/repository/branches/with.1.2.3/protect", user)
+
+        expect(response).to have_http_status(200)
+        expect(json_response['name']).to eq("with.1.2.3")
+        expect(json_response['protected']).to eq(true)
+      end
+
       it 'protects a single branch and developers can push' do
         put api("/projects/#{project.id}/repository/branches/#{branch_name}/protect", user),
             developers_can_push: true
@@ -220,6 +236,14 @@ describe API::Branches, api: true  do
       expect(json_response['protected']).to eq(false)
     end
 
+    it "update branches with dots in branch name" do
+      put api("/projects/#{project.id}/repository/branches/with.1.2.3/unprotect", user)
+
+      expect(response).to have_http_status(200)
+      expect(json_response['name']).to eq("with.1.2.3")
+      expect(json_response['protected']).to eq(false)
+    end
+
     it "returns success when unprotect branch" do
       put api("/projects/#{project.id}/repository/branches/unknown/unprotect", user)
       expect(response).to have_http_status(404)
@@ -292,6 +316,13 @@ describe API::Branches, api: true  do
       expect(json_response['branch_name']).to eq(branch_name)
     end
 
+    it "removes a branch with dots in the branch name" do
+      delete api("/projects/#{project.id}/repository/branches/with.1.2.3", user)
+
+      expect(response).to have_http_status(200)
+      expect(json_response['branch_name']).to eq("with.1.2.3")
+    end
+
     it 'returns 404 if branch not exists' do
       delete api("/projects/#{project.id}/repository/branches/foobar", user)
       expect(response).to have_http_status(404)
diff --git a/spec/requests/api/commits_spec.rb b/spec/requests/api/commits_spec.rb
index e497bce694314c0710e0fd7ae9acae4f8b779c25..964cded917cbf505b08b1b5bd5e17e956441039a 100644
--- a/spec/requests/api/commits_spec.rb
+++ b/spec/requests/api/commits_spec.rb
@@ -456,6 +456,76 @@ describe API::Commits, api: true  do
     end
   end
 
+  describe 'POST :id/repository/commits/:sha/cherry_pick' do
+    let(:master_pickable_commit)  { project.commit('7d3b0f7cff5f37573aea97cebfd5692ea1689924') }
+
+    context 'authorized user' do
+      it 'cherry picks a commit' do
+        post api("/projects/#{project.id}/repository/commits/#{master_pickable_commit.id}/cherry_pick", user), branch: 'master'
+
+        expect(response).to have_http_status(201)
+        expect(json_response['title']).to eq(master_pickable_commit.title)
+        expect(json_response['message']).to eq(master_pickable_commit.message)
+        expect(json_response['author_name']).to eq(master_pickable_commit.author_name)
+        expect(json_response['committer_name']).to eq(user.name)
+      end
+
+      it 'returns 400 if commit is already included in the target branch' do
+        post api("/projects/#{project.id}/repository/commits/#{master_pickable_commit.id}/cherry_pick", user), branch: 'markdown'
+
+        expect(response).to have_http_status(400)
+        expect(json_response['message']).to eq('Sorry, we cannot cherry-pick this commit automatically.
+                     A cherry-pick may have already been performed with this commit, or a more recent commit may have updated some of its content.')
+      end
+
+      it 'returns 400 if you are not allowed to push to the target branch' do
+        project.team << [user2, :developer]
+        protected_branch = create(:protected_branch, project: project, name: 'feature')
+
+        post api("/projects/#{project.id}/repository/commits/#{master_pickable_commit.id}/cherry_pick", user2), branch: protected_branch.name
+
+        expect(response).to have_http_status(400)
+        expect(json_response['message']).to eq('You are not allowed to push into this branch')
+      end
+
+      it 'returns 400 for missing parameters' do
+        post api("/projects/#{project.id}/repository/commits/#{master_pickable_commit.id}/cherry_pick", user)
+
+        expect(response).to have_http_status(400)
+        expect(json_response['error']).to eq('branch is missing')
+      end
+
+      it 'returns 404 if commit is not found' do
+        post api("/projects/#{project.id}/repository/commits/abcd0123/cherry_pick", user), branch: 'master'
+
+        expect(response).to have_http_status(404)
+        expect(json_response['message']).to eq('404 Commit Not Found')
+      end
+
+      it 'returns 404 if branch is not found' do
+        post api("/projects/#{project.id}/repository/commits/#{master_pickable_commit.id}/cherry_pick", user), branch: 'foo'
+
+        expect(response).to have_http_status(404)
+        expect(json_response['message']).to eq('404 Branch Not Found')
+      end
+
+      it 'returns 400 for missing parameters' do
+        post api("/projects/#{project.id}/repository/commits/#{master_pickable_commit.id}/cherry_pick", user)
+
+        expect(response).to have_http_status(400)
+        expect(json_response['error']).to eq('branch is missing')
+      end
+    end
+
+    context 'unauthorized user' do
+      it 'does not cherry pick the commit' do
+        post api("/projects/#{project.id}/repository/commits/#{master_pickable_commit.id}/cherry_pick"), branch: 'master'
+
+        expect(response).to have_http_status(401)
+      end
+    end
+  end
+
   describe 'Post comment to commit' do
     context 'authorized user' do
       it 'returns comment' do
diff --git a/spec/requests/api/groups_spec.rb b/spec/requests/api/groups_spec.rb
index 548ed8e1892382cef606224edf45a29a7a57b650..a75ba824e85412bef0b39726108de4e5e3a62ade 100644
--- a/spec/requests/api/groups_spec.rb
+++ b/spec/requests/api/groups_spec.rb
@@ -243,6 +243,28 @@ describe API::Groups, api: true  do
         expect(json_response.length).to eq(2)
         project_names = json_response.map { |proj| proj['name' ] }
         expect(project_names).to match_array([project1.name, project3.name])
+        expect(json_response.first['default_branch']).to be_present
+      end
+
+      it "returns the group's projects with simple representation" do
+        get api("/groups/#{group1.id}/projects", user1), simple: true
+
+        expect(response).to have_http_status(200)
+        expect(json_response.length).to eq(2)
+        project_names = json_response.map { |proj| proj['name' ] }
+        expect(project_names).to match_array([project1.name, project3.name])
+        expect(json_response.first['default_branch']).not_to be_present
+      end
+
+      it 'filters the groups projects' do
+        public_project = create(:project, :public, path: 'test1', group: group1)
+
+        get api("/groups/#{group1.id}/projects", user1), visibility: 'public'
+
+        expect(response).to have_http_status(200)
+        expect(json_response).to be_an(Array)
+        expect(json_response.length).to eq(1)
+        expect(json_response.first['name']).to eq(public_project.name)
       end
 
       it "does not return a non existing group" do
diff --git a/spec/requests/api/api_helpers_spec.rb b/spec/requests/api/helpers_spec.rb
similarity index 85%
rename from spec/requests/api/api_helpers_spec.rb
rename to spec/requests/api/helpers_spec.rb
index 3f34309f419c0ce4b5ed7042b79c29de6f082a12..4035fd97af59a52ff0eafccb8bfaf25d12774912 100644
--- a/spec/requests/api/api_helpers_spec.rb
+++ b/spec/requests/api/helpers_spec.rb
@@ -2,7 +2,6 @@ require 'spec_helper'
 
 describe API::Helpers, api: true do
   include API::Helpers
-  include ApiHelpers
   include SentryHelper
 
   let(:user) { create(:user) }
@@ -13,18 +12,18 @@ describe API::Helpers, api: true do
   let(:env) { { 'REQUEST_METHOD' => 'GET' } }
   let(:request) { Rack::Request.new(env) }
 
-  def set_env(token_usr, identifier)
+  def set_env(user_or_token, identifier)
     clear_env
     clear_param
-    env[API::Helpers::PRIVATE_TOKEN_HEADER] = token_usr.private_token
-    env[API::Helpers::SUDO_HEADER] = identifier
+    env[API::Helpers::PRIVATE_TOKEN_HEADER] = user_or_token.respond_to?(:private_token) ? user_or_token.private_token : user_or_token
+    env[API::Helpers::SUDO_HEADER] = identifier.to_s
   end
 
-  def set_param(token_usr, identifier)
+  def set_param(user_or_token, identifier)
     clear_env
     clear_param
-    params[API::Helpers::PRIVATE_TOKEN_PARAM] = token_usr.private_token
-    params[API::Helpers::SUDO_PARAM] = identifier
+    params[API::Helpers::PRIVATE_TOKEN_PARAM] = user_or_token.respond_to?(:private_token) ? user_or_token.private_token : user_or_token
+    params[API::Helpers::SUDO_PARAM] = identifier.to_s
   end
 
   def clear_env
@@ -163,6 +162,13 @@ describe API::Helpers, api: true do
               expect(current_user).to eq(user)
             end
 
+            it 'memoize the current_user: sudo permissions are not run against the sudoed user' do
+              set_env(admin, user.id)
+
+              expect(current_user).to eq(user)
+              expect(current_user).to eq(user)
+            end
+
             it 'handles sudo to oneself' do
               set_env(admin, admin.id)
 
@@ -294,33 +300,48 @@ describe API::Helpers, api: true do
     end
   end
 
-  describe '.sudo_identifier' do
-    it "returns integers when input is an int" do
-      set_env(admin, '123')
-      expect(sudo_identifier).to eq(123)
-      set_env(admin, '0001234567890')
-      expect(sudo_identifier).to eq(1234567890)
-
-      set_param(admin, '123')
-      expect(sudo_identifier).to eq(123)
-      set_param(admin, '0001234567890')
-      expect(sudo_identifier).to eq(1234567890)
+  describe '.sudo?' do
+    context 'when no sudo env or param is passed' do
+      before do
+        doorkeeper_guard_returns(nil)
+      end
+
+      it 'returns false' do
+        expect(sudo?).to be_falsy
+      end
     end
 
-    it "returns string when input is an is not an int" do
-      set_env(admin, '12.30')
-      expect(sudo_identifier).to eq("12.30")
-      set_env(admin, 'hello')
-      expect(sudo_identifier).to eq('hello')
-      set_env(admin, ' 123')
-      expect(sudo_identifier).to eq(' 123')
-
-      set_param(admin, '12.30')
-      expect(sudo_identifier).to eq("12.30")
-      set_param(admin, 'hello')
-      expect(sudo_identifier).to eq('hello')
-      set_param(admin, ' 123')
-      expect(sudo_identifier).to eq(' 123')
+    context 'when sudo env or param is passed', 'user is not an admin' do
+      before do
+        set_env(user, '123')
+      end
+
+      it 'returns an 403 Forbidden' do
+        expect { sudo? }.to raise_error '403 - {"message"=>"403 Forbidden  - Must be admin to use sudo"}'
+      end
+    end
+
+    context 'when sudo env or param is passed', 'user is admin' do
+      context 'personal access token is used' do
+        before do
+          personal_access_token = create(:personal_access_token, user: admin)
+          set_env(personal_access_token.token, user.id)
+        end
+
+        it 'returns an 403 Forbidden' do
+          expect { sudo? }.to raise_error '403 - {"message"=>"403 Forbidden  - Private token must be specified in order to use sudo"}'
+        end
+      end
+
+      context 'private access token is used' do
+        before do
+          set_env(admin.private_token, user.id)
+        end
+
+        it 'returns true' do
+          expect(sudo?).to be_truthy
+        end
+      end
     end
   end
 
diff --git a/spec/requests/api/merge_requests_spec.rb b/spec/requests/api/merge_requests_spec.rb
index 75b270aa93c4118801eb8e33e4dc6a298b4a3f9f..f032d1b683dd14b7593cac889f261b918125ff58 100644
--- a/spec/requests/api/merge_requests_spec.rb
+++ b/spec/requests/api/merge_requests_spec.rb
@@ -533,6 +533,22 @@ describe API::MergeRequests, api: true  do
       expect(json_response['labels']).to include '?'
       expect(json_response['labels']).to include '&'
     end
+
+    it 'does not update state when title is empty' do
+      put api("/projects/#{project.id}/merge_requests/#{merge_request.id}", user), state_event: 'close', title: nil
+
+      merge_request.reload
+      expect(response).to have_http_status(400)
+      expect(merge_request.state).to eq('opened')
+    end
+
+    it 'does not update state when target_branch is empty' do
+      put api("/projects/#{project.id}/merge_requests/#{merge_request.id}", user), state_event: 'close', target_branch: nil
+
+      merge_request.reload
+      expect(response).to have_http_status(400)
+      expect(merge_request.state).to eq('opened')
+    end
   end
 
   describe "POST /projects/:id/merge_requests/:merge_request_id/comments" do
diff --git a/spec/requests/api/services_spec.rb b/spec/requests/api/services_spec.rb
index d30361f53d4aa0de064bf336d912a1fa3724d422..668e39f9dba983a4e2fe3d7f51543ed1079d9b18 100644
--- a/spec/requests/api/services_spec.rb
+++ b/spec/requests/api/services_spec.rb
@@ -2,6 +2,7 @@ require "spec_helper"
 
 describe API::Services, api: true  do
   include ApiHelpers
+
   let(:user) { create(:user) }
   let(:admin) { create(:admin) }
   let(:user2) { create(:user) }
@@ -98,7 +99,7 @@ describe API::Services, api: true  do
         post api("/projects/#{project.id}/services/idonotexist/trigger")
 
         expect(response).to have_http_status(404)
-        expect(json_response["message"]).to eq("404 Service Not Found")
+        expect(json_response["error"]).to eq("404 Not Found")
       end
     end
 
@@ -114,7 +115,7 @@ describe API::Services, api: true  do
         end
 
         it 'when the service is inactive' do
-          post api("/projects/#{project.id}/services/mattermost_slash_commands/trigger")
+          post api("/projects/#{project.id}/services/mattermost_slash_commands/trigger"), params
 
           expect(response).to have_http_status(404)
         end
diff --git a/spec/requests/api/snippets_spec.rb b/spec/requests/api/snippets_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..f6fb6ea5506cbd44fba039d4d02b11870dd0c474
--- /dev/null
+++ b/spec/requests/api/snippets_spec.rb
@@ -0,0 +1,157 @@
+require 'rails_helper'
+
+describe API::Snippets, api: true do
+  include ApiHelpers
+  let!(:user) { create(:user) }
+
+  describe 'GET /snippets/' do
+    it 'returns snippets available' do
+      public_snippet = create(:personal_snippet, :public, author: user)
+      private_snippet = create(:personal_snippet, :private, author: user)
+      internal_snippet = create(:personal_snippet, :internal, author: user)
+
+      get api("/snippets/", user)
+
+      expect(response).to have_http_status(200)
+      expect(json_response.map { |snippet| snippet['id']} ).to contain_exactly(
+        public_snippet.id,
+        internal_snippet.id,
+        private_snippet.id)
+      expect(json_response.last).to have_key('web_url')
+      expect(json_response.last).to have_key('raw_url')
+    end
+
+    it 'hides private snippets from regular user' do
+      create(:personal_snippet, :private)
+
+      get api("/snippets/", user)
+      expect(response).to have_http_status(200)
+      expect(json_response.size).to eq(0)
+    end
+  end
+
+  describe 'GET /snippets/public' do
+    let!(:other_user) { create(:user) }
+    let!(:public_snippet) { create(:personal_snippet, :public, author: user) }
+    let!(:private_snippet) { create(:personal_snippet, :private, author: user) }
+    let!(:internal_snippet) { create(:personal_snippet, :internal, author: user) }
+    let!(:public_snippet_other) { create(:personal_snippet, :public, author: other_user) }
+    let!(:private_snippet_other) { create(:personal_snippet, :private, author: other_user) }
+    let!(:internal_snippet_other) { create(:personal_snippet, :internal, author: other_user) }
+
+    it 'returns all snippets with public visibility from all users' do
+      get api("/snippets/public", user)
+
+      expect(response).to have_http_status(200)
+      expect(json_response.map { |snippet| snippet['id']} ).to contain_exactly(
+        public_snippet.id,
+        public_snippet_other.id)
+      expect(json_response.map{ |snippet| snippet['web_url']} ).to include(
+        "http://localhost/snippets/#{public_snippet.id}",
+        "http://localhost/snippets/#{public_snippet_other.id}")
+      expect(json_response.map{ |snippet| snippet['raw_url']} ).to include(
+        "http://localhost/snippets/#{public_snippet.id}/raw",
+        "http://localhost/snippets/#{public_snippet_other.id}/raw")
+    end
+  end
+
+  describe 'GET /snippets/:id/raw' do
+    let(:snippet) { create(:personal_snippet, author: user) }
+
+    it 'returns raw text' do
+      get api("/snippets/#{snippet.id}/raw", user)
+
+      expect(response).to have_http_status(200)
+      expect(response.content_type).to eq 'text/plain'
+      expect(response.body).to eq(snippet.content)
+    end
+
+    it 'returns 404 for invalid snippet id' do
+      delete api("/snippets/1234", user)
+
+      expect(response).to have_http_status(404)
+      expect(json_response['message']).to eq('404 Snippet Not Found')
+    end
+  end
+
+  describe 'POST /snippets/' do
+    let(:params) do
+      {
+        title: 'Test Title',
+        file_name: 'test.rb',
+        content: 'puts "hello world"',
+        visibility_level: Gitlab::VisibilityLevel::PUBLIC
+      }
+    end
+
+    it 'creates a new snippet' do
+      expect do
+        post api("/snippets/", user), params
+      end.to change { PersonalSnippet.count }.by(1)
+
+      expect(response).to have_http_status(201)
+      expect(json_response['title']).to eq(params[:title])
+      expect(json_response['file_name']).to eq(params[:file_name])
+    end
+
+    it 'returns 400 for missing parameters' do
+      params.delete(:title)
+
+      post api("/snippets/", user), params
+
+      expect(response).to have_http_status(400)
+    end
+  end
+
+  describe 'PUT /snippets/:id' do
+    let(:other_user) { create(:user) }
+    let(:public_snippet) { create(:personal_snippet, :public, author: user) }
+    it 'updates snippet' do
+      new_content = 'New content'
+
+      put api("/snippets/#{public_snippet.id}", user), content: new_content
+
+      expect(response).to have_http_status(200)
+      public_snippet.reload
+      expect(public_snippet.content).to eq(new_content)
+    end
+
+    it 'returns 404 for invalid snippet id' do
+      put api("/snippets/1234", user), title: 'foo'
+
+      expect(response).to have_http_status(404)
+      expect(json_response['message']).to eq('404 Snippet Not Found')
+    end
+
+    it "returns 404 for another user's snippet" do
+      put api("/snippets/#{public_snippet.id}", other_user), title: 'fubar'
+
+      expect(response).to have_http_status(404)
+      expect(json_response['message']).to eq('404 Snippet Not Found')
+    end
+
+    it 'returns 400 for missing parameters' do
+      put api("/snippets/1234", user)
+
+      expect(response).to have_http_status(400)
+    end
+  end
+
+  describe 'DELETE /snippets/:id' do
+    let!(:public_snippet) { create(:personal_snippet, :public, author: user) }
+    it 'deletes snippet' do
+      expect do
+        delete api("/snippets/#{public_snippet.id}", user)
+
+        expect(response).to have_http_status(204)
+      end.to change { PersonalSnippet.count }.by(-1)
+    end
+
+    it 'returns 404 for invalid snippet id' do
+      delete api("/snippets/1234", user)
+
+      expect(response).to have_http_status(404)
+      expect(json_response['message']).to eq('404 Snippet Not Found')
+    end
+  end
+end
diff --git a/spec/requests/api/users_spec.rb b/spec/requests/api/users_spec.rb
index c37dbfa0a3350bd5ada1a6d31ca08bf07e6f4ba3..9e317f3a7e91498abf37ebff32771bdcc972fdf5 100644
--- a/spec/requests/api/users_spec.rb
+++ b/spec/requests/api/users_spec.rb
@@ -651,13 +651,12 @@ describe API::Users, api: true  do
   end
 
   describe "GET /user" do
-    let(:personal_access_token) { create(:personal_access_token, user: user) }
-    let(:private_token) { user.private_token }
+    let(:personal_access_token) { create(:personal_access_token, user: user).token }
 
     context 'with regular user' do
       context 'with personal access token' do
         it 'returns 403 without private token when sudo is defined' do
-          get api("/user?private_token=#{personal_access_token.token}&sudo=#{user.id}")
+          get api("/user?private_token=#{personal_access_token}&sudo=123")
 
           expect(response).to have_http_status(403)
         end
@@ -665,7 +664,7 @@ describe API::Users, api: true  do
 
       context 'with private token' do
         it 'returns 403 without private token when sudo defined' do
-          get api("/user?private_token=#{private_token}&sudo=#{user.id}")
+          get api("/user?private_token=#{user.private_token}&sudo=123")
 
           expect(response).to have_http_status(403)
         end
@@ -676,40 +675,44 @@ describe API::Users, api: true  do
 
         expect(response).to have_http_status(200)
         expect(response).to match_response_schema('user/public')
+        expect(json_response['id']).to eq(user.id)
       end
     end
 
     context 'with admin' do
-      let(:user) { create(:admin) }
+      let(:admin_personal_access_token) { create(:personal_access_token, user: admin).token }
 
       context 'with personal access token' do
         it 'returns 403 without private token when sudo defined' do
-          get api("/user?private_token=#{personal_access_token.token}&sudo=#{user.id}")
+          get api("/user?private_token=#{admin_personal_access_token}&sudo=#{user.id}")
 
           expect(response).to have_http_status(403)
         end
 
-        it 'returns current user without private token when sudo not defined' do
-          get api("/user?private_token=#{personal_access_token.token}")
+        it 'returns initial current user without private token when sudo not defined' do
+          get api("/user?private_token=#{admin_personal_access_token}")
 
           expect(response).to have_http_status(200)
           expect(response).to match_response_schema('user/public')
+          expect(json_response['id']).to eq(admin.id)
         end
       end
 
       context 'with private token' do
-        it 'returns current user with private token when sudo defined' do
-          get api("/user?private_token=#{private_token}&sudo=#{user.id}")
+        it 'returns sudoed user with private token when sudo defined' do
+          get api("/user?private_token=#{admin.private_token}&sudo=#{user.id}")
 
           expect(response).to have_http_status(200)
           expect(response).to match_response_schema('user/login')
+          expect(json_response['id']).to eq(user.id)
         end
 
-        it 'returns current user without private token when sudo not defined' do
-          get api("/user?private_token=#{private_token}")
+        it 'returns initial current user without private token when sudo not defined' do
+          get api("/user?private_token=#{admin.private_token}")
 
           expect(response).to have_http_status(200)
           expect(response).to match_response_schema('user/public')
+          expect(json_response['id']).to eq(admin.id)
         end
       end
     end
diff --git a/spec/routing/admin_routing_spec.rb b/spec/routing/admin_routing_spec.rb
index 69eeb45ed71589744275663176dae332d374d29b..661b671301e8e3f13bd42526b61a8f59d2e88304 100644
--- a/spec/routing/admin_routing_spec.rb
+++ b/spec/routing/admin_routing_spec.rb
@@ -66,7 +66,8 @@ describe Admin::ProjectsController, "routing" do
   end
 
   it "to #show" do
-    expect(get("/admin/projects/gitlab")).to route_to('admin/projects#show', namespace_id: 'gitlab')
+    expect(get("/admin/projects/gitlab/gitlab-ce")).to route_to('admin/projects#show', namespace_id: 'gitlab', id: 'gitlab-ce')
+    expect(get("/admin/projects/gitlab/subgroup/gitlab-ce")).to route_to('admin/projects#show', namespace_id: 'gitlab/subgroup', id: 'gitlab-ce')
   end
 end
 
@@ -119,3 +120,14 @@ describe Admin::HealthCheckController, "routing" do
     expect(get("/admin/health_check")).to route_to('admin/health_check#show')
   end
 end
+
+describe Admin::GroupsController, "routing" do
+  it "to #index" do
+    expect(get("/admin/groups")).to route_to('admin/groups#index')
+  end
+
+  it "to #show" do
+    expect(get("/admin/groups/gitlab")).to route_to('admin/groups#show', id: 'gitlab')
+    expect(get("/admin/groups/gitlab/subgroup")).to route_to('admin/groups#show', id: 'gitlab/subgroup')
+  end
+end
diff --git a/spec/services/system_note_service_spec.rb b/spec/services/system_note_service_spec.rb
index 90b7e62bc6f97e6106fcac5b17a0af08afa86a7a..0e8adb687212bd1149c986788b0e079c584dc4ad 100644
--- a/spec/services/system_note_service_spec.rb
+++ b/spec/services/system_note_service_spec.rb
@@ -694,7 +694,7 @@ describe SystemNoteService, services: true do
 
     describe "existing reference" do
       before do
-        message = "[#{author.name}|http://localhost/#{author.username}] mentioned this issue in [a commit of #{project.path_with_namespace}|http://localhost/#{project.path_with_namespace}/commit/#{commit.id}]:\n'#{commit.title}'"
+        message = "[#{author.name}|http://localhost/#{author.username}] mentioned this issue in [a commit of #{project.path_with_namespace}|http://localhost/#{project.path_with_namespace}/commit/#{commit.id}]:\n'#{commit.title.chomp}'"
         allow_any_instance_of(JIRA::Resource::Issue).to receive(:comments).and_return([OpenStruct.new(body: message)])
       end
 
diff --git a/spec/support/login_helpers.rb b/spec/support/login_helpers.rb
index c0b3e83244ddd6fdca29dd7e77d8bba6629828d5..ad1eed5b369de544dde7af72a6b3c14924a4a8a2 100644
--- a/spec/support/login_helpers.rb
+++ b/spec/support/login_helpers.rb
@@ -75,7 +75,8 @@ module LoginHelpers
   def logout
     find(".header-user-dropdown-toggle").click
     click_link "Sign out"
-    expect(page).to have_content('Signed out successfully')
+    # check the sign_in button
+    expect(page).to have_button('Sign in')
   end
 
   # Logout without JavaScript driver
diff --git a/spec/support/services_shared_context.rb b/spec/support/services_shared_context.rb
index d1c999cad4de8d2625964478f90b86f6d7b55cd2..66c93890e31ef202211c7d1643f206f8e7567ddf 100644
--- a/spec/support/services_shared_context.rb
+++ b/spec/support/services_shared_context.rb
@@ -16,8 +16,14 @@ Service.available_services_names.each do |service|
           hash.merge!(k => 'secrettoken')
         elsif k =~ /^(.*_url|url|webhook)/
           hash.merge!(k => "http://example.com")
+        elsif service_klass.method_defined?("#{k}?")
+          hash.merge!(k => true)
         elsif service == 'irker' && k == :recipients
           hash.merge!(k => 'irc://irc.network.net:666/#channel')
+        elsif service == 'irker' && k == :server_port
+          hash.merge!(k => 1234)
+        elsif service == 'jira' && k == :jira_issue_transition_id
+          hash.merge!(k => 1234)
         else
           hash.merge!(k => "someword")
         end
diff --git a/spec/views/projects/commit/_commit_box.html.haml_spec.rb b/spec/views/projects/commit/_commit_box.html.haml_spec.rb
index 16bf0698c4be4b82646463aba56ac8a74757a458..e741e3cf9b6c0a34d8a437fe80cde9ea97d8f188 100644
--- a/spec/views/projects/commit/_commit_box.html.haml_spec.rb
+++ b/spec/views/projects/commit/_commit_box.html.haml_spec.rb
@@ -13,7 +13,7 @@ describe 'projects/commit/_commit_box.html.haml' do
   it 'shows the commit SHA' do
     render
 
-    expect(rendered).to have_text("Commit #{Commit.truncate_sha(project.commit.sha)}")
+    expect(rendered).to have_text("#{Commit.truncate_sha(project.commit.sha)}")
   end
 
   it 'shows the last pipeline that ran for the commit' do
diff --git a/spec/views/projects/pipelines/show.html.haml_spec.rb b/spec/views/projects/pipelines/show.html.haml_spec.rb
index bf027499c94c59f6165510fea384070517f591a1..a066ea078e65bd704aa066e50a8ccd6f9342af75 100644
--- a/spec/views/projects/pipelines/show.html.haml_spec.rb
+++ b/spec/views/projects/pipelines/show.html.haml_spec.rb
@@ -28,7 +28,7 @@ describe 'projects/pipelines/show' do
   it 'shows a graph with grouped stages' do
     render
 
-    expect(rendered).to have_css('.pipeline-graph')
+    expect(rendered).to have_css('.js-pipeline-graph')
     expect(rendered).to have_css('.grouped-pipeline-dropdown')
 
     # stages