diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index dab1b220bfb7823688f597459826f8292db2849c..aa62a86d31d6e30e02e8fa4c14a8e616e9c375dc 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -264,11 +264,25 @@ spinach mysql 9 10: *spinach-knapsack-mysql
 static-analysis:
   <<: *ruby-static-analysis
   <<: *dedicated-runner
-  <<: *except-docs
   stage: test
   script:
     - scripts/static-analysis
 
+docs:check:links:
+  image: "registry.gitlab.com/gitlab-org/gitlab-build-images:nanoc-bootstrap-ruby-2.4-alpine"
+  stage: test
+  <<: *dedicated-runner
+  cache: {}
+  dependencies: []
+  before_script: []
+  script:
+    - mv doc/ /nanoc/content/
+    - cd /nanoc
+    # Build HTML from Markdown
+    - bundle exec nanoc
+    # Check the internal links
+    - bundle exec nanoc check internal_links
+
 downtime_check:
   <<: *rake-exec
   except:
@@ -300,39 +314,38 @@ ee_compat_check:
 .db-migrate-reset: &db-migrate-reset
   stage: test
   <<: *dedicated-runner
+  <<: *except-docs
   script:
     - bundle exec rake db:migrate:reset
 
-db:migrate:reset pg:
+rake pg db:migrate:reset:
   <<: *db-migrate-reset
   <<: *use-pg
-  <<: *except-docs
 
-db:migrate:reset mysql:
+rake mysql db:migrate:reset:
   <<: *db-migrate-reset
   <<: *use-mysql
-  <<: *except-docs
 
 .db-rollback: &db-rollback
   stage: test
   <<: *dedicated-runner
+  <<: *except-docs
   script:
     - bundle exec rake db:rollback STEP=120
     - bundle exec rake db:migrate
 
-db:rollback pg:
+rake pg db:rollback:
   <<: *db-rollback
   <<: *use-pg
-  <<: *except-docs
 
-db:rollback mysql:
+rake mysql db:rollback:
   <<: *db-rollback
   <<: *use-mysql
-  <<: *except-docs
 
 .db-seed_fu: &db-seed_fu
   stage: test
   <<: *dedicated-runner
+  <<: *except-docs
   variables:
     SIZE: "1"
     SETUP_DB: "false"
@@ -347,17 +360,15 @@ db:rollback mysql:
     paths:
       - log/development.log
 
-db:seed_fu pg:
+rake pg db:seed_fu:
   <<: *db-seed_fu
   <<: *use-pg
-  <<: *except-docs
 
-db:seed_fu mysql:
+rake mysql db:seed_fu:
   <<: *db-seed_fu
   <<: *use-mysql
-  <<: *except-docs
 
-gitlab:assets:compile:
+rake gitlab:assets:compile:
   stage: test
   <<: *dedicated-runner
   <<: *except-docs
@@ -377,7 +388,7 @@ gitlab:assets:compile:
     paths:
     - webpack-report/
 
-karma:
+rake karma:
   cache:
     paths:
       - vendor/ruby
@@ -396,21 +407,6 @@ karma:
     paths:
     - coverage-javascript/
 
-docs:check:links:
-  image: "registry.gitlab.com/gitlab-org/gitlab-build-images:nanoc-bootstrap-ruby-2.4-alpine"
-  stage: test
-  <<: *dedicated-runner
-  cache: {}
-  dependencies: []
-  before_script: []
-  script:
-    - mv doc/ /nanoc/content/
-    - cd /nanoc
-    # Build HTML from Markdown
-    - bundle exec nanoc
-    # Check the internal links
-    - bundle exec nanoc check internal_links
-
 bundler:audit:
   stage: test
   <<: *ruby-static-analysis
@@ -443,11 +439,11 @@ bundler:audit:
     - . scripts/prepare_build.sh
     - bundle exec rake db:migrate
 
-migration path pg:
+migration pg paths:
   <<: *migration-paths
   <<: *use-pg
 
-migration path mysql:
+migration mysql paths:
   <<: *migration-paths
   <<: *use-mysql
 
@@ -502,30 +498,14 @@ trigger_docs:
     - master@gitlab-org/gitlab-ce
     - master@gitlab-org/gitlab-ee
 
-# Notify slack in the end
-notify:slack:
-  stage: post-test
-  <<: *dedicated-runner
-  variables:
-    SETUP_DB: "false"
-    USE_BUNDLE_INSTALL: "false"
-  script:
-    - ./scripts/notify_slack.sh "#development" "Build on \`$CI_COMMIT_REF_NAME\` failed! Commit \`$(git log -1 --oneline)\` See <https://gitlab.com/gitlab-org/$(basename "$PWD")/commit/"$CI_COMMIT_SHA"/pipelines>"
-  when: on_failure
-  only:
-    - master@gitlab-org/gitlab-ce
-    - tags@gitlab-org/gitlab-ce
-    - master@gitlab-org/gitlab-ee
-    - tags@gitlab-org/gitlab-ee
-
 pages:
   before_script: []
   stage: pages
   <<: *dedicated-runner
   dependencies:
     - coverage
-    - karma
-    - gitlab:assets:compile
+    - rake karma
+    - rake gitlab:assets:compile
     - lint:javascript:report
   script:
     - mv public/ .public/
diff --git a/.rubocop.yml b/.rubocop.yml
index 8c43f6909cf24aaba4f65e5593e02b03b4f067e5..e53af97a92cbde17ce059e32b9a88de66bdf4923 100644
--- a/.rubocop.yml
+++ b/.rubocop.yml
@@ -961,7 +961,7 @@ RSpec/DescribeSymbol:
 # Checks that the second argument to top level describe is the tested method
 # name.
 RSpec/DescribedClass:
-  Enabled: false
+  Enabled: true
 
 # Checks for long example.
 RSpec/ExampleLength:
diff --git a/app/assets/images/ci_favicons/dev/favicon_status_canceled.ico b/app/assets/images/ci_favicons/dev/favicon_status_canceled.ico
new file mode 100644
index 0000000000000000000000000000000000000000..4af3582b60d2fa40201791f2865491fb84e0ae76
Binary files /dev/null and b/app/assets/images/ci_favicons/dev/favicon_status_canceled.ico differ
diff --git a/app/assets/images/ci_favicons/dev/favicon_status_created.ico b/app/assets/images/ci_favicons/dev/favicon_status_created.ico
new file mode 100644
index 0000000000000000000000000000000000000000..13639da2e8a343576006037c7ca2345beee1fd20
Binary files /dev/null and b/app/assets/images/ci_favicons/dev/favicon_status_created.ico differ
diff --git a/app/assets/images/ci_favicons/dev/favicon_status_failed.ico b/app/assets/images/ci_favicons/dev/favicon_status_failed.ico
new file mode 100644
index 0000000000000000000000000000000000000000..5f0e711b104b2e4bf88a3c726cdb9b7f17a09844
Binary files /dev/null and b/app/assets/images/ci_favicons/dev/favicon_status_failed.ico differ
diff --git a/app/assets/images/ci_favicons/dev/favicon_status_manual.ico b/app/assets/images/ci_favicons/dev/favicon_status_manual.ico
new file mode 100644
index 0000000000000000000000000000000000000000..8b1168a12670142ec5164d5a8f5baea951b6d7d3
Binary files /dev/null and b/app/assets/images/ci_favicons/dev/favicon_status_manual.ico differ
diff --git a/app/assets/images/ci_favicons/dev/favicon_status_not_found.ico b/app/assets/images/ci_favicons/dev/favicon_status_not_found.ico
new file mode 100644
index 0000000000000000000000000000000000000000..ed19b69e1c5bef5fc97b8d8273886f6fbbd028f2
Binary files /dev/null and b/app/assets/images/ci_favicons/dev/favicon_status_not_found.ico differ
diff --git a/app/assets/images/ci_favicons/dev/favicon_status_pending.ico b/app/assets/images/ci_favicons/dev/favicon_status_pending.ico
new file mode 100644
index 0000000000000000000000000000000000000000..5dfefd4cc5a284aa507915ee8dd8efb3ab2a34b4
Binary files /dev/null and b/app/assets/images/ci_favicons/dev/favicon_status_pending.ico differ
diff --git a/app/assets/images/ci_favicons/dev/favicon_status_running.ico b/app/assets/images/ci_favicons/dev/favicon_status_running.ico
new file mode 100644
index 0000000000000000000000000000000000000000..a41539c0e3e8fe178f3ea88fbce6c66b00df3c2c
Binary files /dev/null and b/app/assets/images/ci_favicons/dev/favicon_status_running.ico differ
diff --git a/app/assets/images/ci_favicons/dev/favicon_status_skipped.ico b/app/assets/images/ci_favicons/dev/favicon_status_skipped.ico
new file mode 100644
index 0000000000000000000000000000000000000000..2c1ae552b930be794abee0286d25a11fbeafb261
Binary files /dev/null and b/app/assets/images/ci_favicons/dev/favicon_status_skipped.ico differ
diff --git a/app/assets/images/ci_favicons/dev/favicon_status_success.ico b/app/assets/images/ci_favicons/dev/favicon_status_success.ico
new file mode 100644
index 0000000000000000000000000000000000000000..70f0ca61eca731f254935c0babc2d4002b0c7518
Binary files /dev/null and b/app/assets/images/ci_favicons/dev/favicon_status_success.ico differ
diff --git a/app/assets/images/ci_favicons/dev/favicon_status_warning.ico b/app/assets/images/ci_favicons/dev/favicon_status_warning.ico
new file mode 100644
index 0000000000000000000000000000000000000000..db289e03eb15e0b5d799c1e58864e5941ca66da0
Binary files /dev/null and b/app/assets/images/ci_favicons/dev/favicon_status_warning.ico differ
diff --git a/app/assets/images/ci_favicons/favicon_status_canceled.ico b/app/assets/images/ci_favicons/favicon_status_canceled.ico
old mode 100755
new mode 100644
index 5a19458f2a2e0e5585f3932f01736cf838b86c76..23adcffff5082fb84b1dc17f547875d7d3792251
Binary files a/app/assets/images/ci_favicons/favicon_status_canceled.ico and b/app/assets/images/ci_favicons/favicon_status_canceled.ico differ
diff --git a/app/assets/images/ci_favicons/favicon_status_created.ico b/app/assets/images/ci_favicons/favicon_status_created.ico
old mode 100755
new mode 100644
index 4dca9640cb373737df8c7cce41af6dd8160b3913..f9d93b390d8fe6dd63003ea7ebd95c057e7ebc8e
Binary files a/app/assets/images/ci_favicons/favicon_status_created.ico and b/app/assets/images/ci_favicons/favicon_status_created.ico differ
diff --git a/app/assets/images/ci_favicons/favicon_status_failed.ico b/app/assets/images/ci_favicons/favicon_status_failed.ico
old mode 100755
new mode 100644
index c961ff9a69baa8f3cbd63ab9083c05554482052e..28a22ebf724de909925b6f1b614a7385cbbf86a7
Binary files a/app/assets/images/ci_favicons/favicon_status_failed.ico and b/app/assets/images/ci_favicons/favicon_status_failed.ico differ
diff --git a/app/assets/images/ci_favicons/favicon_status_manual.ico b/app/assets/images/ci_favicons/favicon_status_manual.ico
old mode 100755
new mode 100644
index 5fbbc99ea7cc79f1d48ee5e6f1ef88d2829b2c6f..dbbf1abf30c354d547fe599db7e884815f7ef7e6
Binary files a/app/assets/images/ci_favicons/favicon_status_manual.ico and b/app/assets/images/ci_favicons/favicon_status_manual.ico differ
diff --git a/app/assets/images/ci_favicons/favicon_status_not_found.ico b/app/assets/images/ci_favicons/favicon_status_not_found.ico
old mode 100755
new mode 100644
index 21afa9c72e6ee7432b988907f1d8f90b70cd02a2..49b9b232dd1a27e39b1d3c82cbf2292239402d09
Binary files a/app/assets/images/ci_favicons/favicon_status_not_found.ico and b/app/assets/images/ci_favicons/favicon_status_not_found.ico differ
diff --git a/app/assets/images/ci_favicons/favicon_status_pending.ico b/app/assets/images/ci_favicons/favicon_status_pending.ico
old mode 100755
new mode 100644
index 8be32dab85a13884518d830f2e3dbf788b75f14f..05962f3f148842ece8c1fe9c996abb1692776672
Binary files a/app/assets/images/ci_favicons/favicon_status_pending.ico and b/app/assets/images/ci_favicons/favicon_status_pending.ico differ
diff --git a/app/assets/images/ci_favicons/favicon_status_running.ico b/app/assets/images/ci_favicons/favicon_status_running.ico
old mode 100755
new mode 100644
index f328ff1a5ed098a18d98abf27676e60128e75303..7fa3d4d48d43f7ad2a4d76dd51edda2a863904c0
Binary files a/app/assets/images/ci_favicons/favicon_status_running.ico and b/app/assets/images/ci_favicons/favicon_status_running.ico differ
diff --git a/app/assets/images/ci_favicons/favicon_status_skipped.ico b/app/assets/images/ci_favicons/favicon_status_skipped.ico
old mode 100755
new mode 100644
index b4394e1b4af94ab7efe98ed2c5dad06c49434c7f..b0c26b62068063c7d9a343be7d5d2feef25a4c42
Binary files a/app/assets/images/ci_favicons/favicon_status_skipped.ico and b/app/assets/images/ci_favicons/favicon_status_skipped.ico differ
diff --git a/app/assets/images/ci_favicons/favicon_status_success.ico b/app/assets/images/ci_favicons/favicon_status_success.ico
old mode 100755
new mode 100644
index 4f436c9524219a91bde74d32eda45fd37c6acef1..b150960b5befbb2a8ae5a61cdcb64437ac9a98d8
Binary files a/app/assets/images/ci_favicons/favicon_status_success.ico and b/app/assets/images/ci_favicons/favicon_status_success.ico differ
diff --git a/app/assets/images/ci_favicons/favicon_status_warning.ico b/app/assets/images/ci_favicons/favicon_status_warning.ico
old mode 100755
new mode 100644
index 805cc20cdec49d82bc29a06676e8263a89425a9d..7e71d71684df725e091c00c67a6d763c898bdedd
Binary files a/app/assets/images/ci_favicons/favicon_status_warning.ico and b/app/assets/images/ci_favicons/favicon_status_warning.ico differ
diff --git a/app/assets/javascripts/boards/components/board_sidebar.js b/app/assets/javascripts/boards/components/board_sidebar.js
index 004bac09f598bcacf345a74d6eb948def8acc227..f0066d4ec5d722b65b60f32ef5e8e7a067921e67 100644
--- a/app/assets/javascripts/boards/components/board_sidebar.js
+++ b/app/assets/javascripts/boards/components/board_sidebar.js
@@ -27,6 +27,9 @@ gl.issueBoards.BoardSidebar = Vue.extend({
   computed: {
     showSidebar () {
       return Object.keys(this.issue).length;
+    },
+    assigneeId() {
+      return this.issue.assignee ? this.issue.assignee.id : 0;
     }
   },
   watch: {
diff --git a/app/assets/javascripts/monitoring/constants.js b/app/assets/javascripts/monitoring/constants.js
new file mode 100644
index 0000000000000000000000000000000000000000..c3a8da52404f6b4817c86ace0d8880422ad2f1f0
--- /dev/null
+++ b/app/assets/javascripts/monitoring/constants.js
@@ -0,0 +1,4 @@
+import d3 from 'd3';
+
+export const dateFormat = d3.time.format('%b %d, %Y');
+export const timeFormat = d3.time.format('%H:%M%p');
diff --git a/app/assets/javascripts/monitoring/deployments.js b/app/assets/javascripts/monitoring/deployments.js
new file mode 100644
index 0000000000000000000000000000000000000000..fc92ab61b31d83e0c3a680ff3ef635f194cdf33e
--- /dev/null
+++ b/app/assets/javascripts/monitoring/deployments.js
@@ -0,0 +1,211 @@
+/* global Flash */
+import d3 from 'd3';
+import {
+  dateFormat,
+  timeFormat,
+} from './constants';
+
+export default class Deployments {
+  constructor(width, height) {
+    this.width = width;
+    this.height = height;
+
+    this.endpoint = document.getElementById('js-metrics').dataset.deploymentEndpoint;
+
+    this.createGradientDef();
+  }
+
+  init(chartData) {
+    this.chartData = chartData;
+
+    this.x = d3.time.scale().range([0, this.width]);
+    this.x.domain(d3.extent(this.chartData, d => d.time));
+
+    this.charts = d3.selectAll('.prometheus-graph');
+
+    this.getData();
+  }
+
+  getData() {
+    $.ajax({
+      url: this.endpoint,
+      dataType: 'JSON',
+    })
+    .fail(() => new Flash('Error getting deployment information.'))
+    .done((data) => {
+      this.data = data.deployments.reduce((deploymentDataArray, deployment) => {
+        const time = new Date(deployment.created_at);
+        const xPos = Math.floor(this.x(time));
+
+        time.setSeconds(this.chartData[0].time.getSeconds());
+
+        if (xPos >= 0) {
+          deploymentDataArray.push({
+            id: deployment.id,
+            time,
+            sha: deployment.sha,
+            tag: deployment.tag,
+            ref: deployment.ref.name,
+            xPos,
+          });
+        }
+
+        return deploymentDataArray;
+      }, []);
+
+      this.plotData();
+    });
+  }
+
+  plotData() {
+    this.charts.each((d, i) => {
+      const svg = d3.select(this.charts[0][i]);
+      const chart = svg.select('.graph-container');
+      const key = svg.node().getAttribute('graph-type');
+
+      this.createLine(chart, key);
+      this.createDeployInfoBox(chart, key);
+    });
+  }
+
+  createGradientDef() {
+    const defs = d3.select('body')
+      .append('svg')
+      .attr({
+        height: 0,
+        width: 0,
+      })
+      .append('defs');
+
+    defs.append('linearGradient')
+      .attr({
+        id: 'shadow-gradient',
+      })
+      .append('stop')
+      .attr({
+        offset: '0%',
+        'stop-color': '#000',
+        'stop-opacity': 0.4,
+      })
+      .select(this.selectParentNode)
+      .append('stop')
+      .attr({
+        offset: '100%',
+        'stop-color': '#000',
+        'stop-opacity': 0,
+      });
+  }
+
+  createLine(chart, key) {
+    chart.append('g')
+      .attr({
+        class: 'deploy-info',
+      })
+      .selectAll('.deploy-info')
+      .data(this.data)
+      .enter()
+      .append('g')
+      .attr({
+        class: d => `deploy-info-${d.id}-${key}`,
+        transform: d => `translate(${Math.floor(d.xPos) + 1}, 0)`,
+      })
+      .append('rect')
+      .attr({
+        x: 1,
+        y: 0,
+        height: this.height + 1,
+        width: 3,
+        fill: 'url(#shadow-gradient)',
+      })
+      .select(this.selectParentNode)
+      .append('line')
+      .attr({
+        class: 'deployment-line',
+        x1: 0,
+        x2: 0,
+        y1: 0,
+        y2: this.height + 1,
+      });
+  }
+
+  createDeployInfoBox(chart, key) {
+    chart.selectAll('.deploy-info')
+      .selectAll('.js-deploy-info-box')
+      .data(this.data)
+      .enter()
+      .select(d => document.querySelector(`.deploy-info-${d.id}-${key}`))
+      .append('svg')
+      .attr({
+        class: 'js-deploy-info-box hidden',
+        x: 3,
+        y: 0,
+        width: 92,
+        height: 60,
+      })
+      .append('rect')
+      .attr({
+        class: 'rect-text-metric deploy-info-rect rect-metric',
+        x: 1,
+        y: 1,
+        rx: 2,
+        width: 90,
+        height: 58,
+      })
+      .select(this.selectParentNode)
+      .append('g')
+      .attr({
+        transform: 'translate(5, 2)',
+      })
+      .append('text')
+      .attr({
+        class: 'deploy-info-text text-metric-bold',
+      })
+      .text(Deployments.refText)
+      .select(this.selectParentNode)
+      .append('text')
+      .attr({
+        class: 'deploy-info-text',
+        y: 18,
+      })
+      .text(d => dateFormat(d.time))
+      .select(this.selectParentNode)
+      .append('text')
+      .attr({
+        class: 'deploy-info-text text-metric-bold',
+        y: 38,
+      })
+      .text(d => timeFormat(d.time));
+  }
+
+  static toggleDeployTextbox(deploy, key, showInfoBox) {
+    d3.selectAll(`.deploy-info-${deploy.id}-${key} .js-deploy-info-box`)
+      .classed('hidden', !showInfoBox);
+  }
+
+  mouseOverDeployInfo(mouseXPos, key) {
+    if (!this.data) return false;
+
+    let dataFound = false;
+
+    this.data.forEach((d) => {
+      if (d.xPos >= mouseXPos - 10 && d.xPos <= mouseXPos + 10 && !dataFound) {
+        dataFound = d.xPos + 1;
+
+        Deployments.toggleDeployTextbox(d, key, true);
+      } else {
+        Deployments.toggleDeployTextbox(d, key, false);
+      }
+    });
+
+    return dataFound;
+  }
+
+  /* `this` is bound to the D3 node */
+  selectParentNode() {
+    return this.parentNode;
+  }
+
+  static refText(d) {
+    return d.tag ? d.ref : d.sha.slice(0, 6);
+  }
+}
diff --git a/app/assets/javascripts/monitoring/prometheus_graph.js b/app/assets/javascripts/monitoring/prometheus_graph.js
index 78bb0e6fb4738e7978979b4d6c833f752721701d..6af887691292e1de6e3c013a715417e78c8d101c 100644
--- a/app/assets/javascripts/monitoring/prometheus_graph.js
+++ b/app/assets/javascripts/monitoring/prometheus_graph.js
@@ -3,16 +3,20 @@
 
 import d3 from 'd3';
 import statusCodes from '~/lib/utils/http_status';
-import { formatRelevantDigits } from '~/lib/utils/number_utils';
+import Deployments from './deployments';
+import '../lib/utils/common_utils';
+import { formatRelevantDigits } from '../lib/utils/number_utils';
 import '../flash';
+import {
+  dateFormat,
+  timeFormat,
+} from './constants';
 
 const prometheusContainer = '.prometheus-container';
 const prometheusParentGraphContainer = '.prometheus-graphs';
 const prometheusGraphsContainer = '.prometheus-graph';
 const prometheusStatesContainer = '.prometheus-state';
 const metricsEndpoint = 'metrics.json';
-const timeFormat = d3.time.format('%H:%M');
-const dayFormat = d3.time.format('%b %e, %a');
 const bisectDate = d3.bisector(d => d.time).left;
 const extraAddedWidthParent = 100;
 
@@ -36,6 +40,7 @@ class PrometheusGraph {
       this.width = parentContainerWidth - this.margin.left - this.margin.right;
       this.height = this.originalHeight - this.margin.top - this.margin.bottom;
       this.backOffRequestCounter = 0;
+      this.deployments = new Deployments(this.width, this.height);
       this.configureGraph();
       this.init();
     } else {
@@ -74,6 +79,12 @@ class PrometheusGraph {
         $(prometheusParentGraphContainer).show();
         this.transformData(metricsResponse);
         this.createGraph();
+
+        const firstMetricData = this.graphSpecificProperties[
+          Object.keys(this.graphSpecificProperties)[0]
+        ].data;
+
+        this.deployments.init(firstMetricData);
       }
     });
   }
@@ -96,6 +107,7 @@ class PrometheusGraph {
       .attr('width', this.width + this.margin.left + this.margin.right)
       .attr('height', this.height + this.margin.bottom + this.margin.top)
       .append('g')
+      .attr('class', 'graph-container')
         .attr('transform', `translate(${this.margin.left},${this.margin.top})`);
 
     const axisLabelContainer = d3.select(prometheusGraphContainer)
@@ -116,6 +128,7 @@ class PrometheusGraph {
       .scale(y)
       .ticks(this.commonGraphProperties.axis_no_ticks)
       .tickSize(-this.width)
+      .outerTickSize(0)
       .orient('left');
 
     this.createAxisLabelContainers(axisLabelContainer, key);
@@ -248,7 +261,8 @@ class PrometheusGraph {
       const d1 = currentGraphProps.data[overlayIndex];
       const evalTime = timeValueOverlay - d0.time > d1.time - timeValueOverlay;
       const currentData = evalTime ? d1 : d0;
-      const currentTimeCoordinate = currentGraphProps.xScale(currentData.time);
+      const currentTimeCoordinate = Math.floor(currentGraphProps.xScale(currentData.time));
+      const currentDeployXPos = this.deployments.mouseOverDeployInfo(currentXCoordinate, key);
       const currentPrometheusGraphContainer = `${prometheusGraphsContainer}[graph-type=${key}]`;
       const maxValueFromData = d3.max(currentGraphProps.data.map(metricValue => metricValue.value));
       const maxMetricValue = currentGraphProps.yScale(maxValueFromData);
@@ -256,13 +270,12 @@ class PrometheusGraph {
       // Clear up all the pieces of the flag
       d3.selectAll(`${currentPrometheusGraphContainer} .selected-metric-line`).remove();
       d3.selectAll(`${currentPrometheusGraphContainer} .circle-metric`).remove();
-      d3.selectAll(`${currentPrometheusGraphContainer} .rect-text-metric`).remove();
-      d3.selectAll(`${currentPrometheusGraphContainer} .text-metric`).remove();
+      d3.selectAll(`${currentPrometheusGraphContainer} .rect-text-metric:not(.deploy-info-rect)`).remove();
 
       const currentChart = d3.select(currentPrometheusGraphContainer).select('g');
       currentChart.append('line')
-      .attr('class', 'selected-metric-line')
       .attr({
+        class: `${currentDeployXPos ? 'hidden' : ''} selected-metric-line`,
         x1: currentTimeCoordinate,
         y1: currentGraphProps.yScale(0),
         x2: currentTimeCoordinate,
@@ -272,33 +285,45 @@ class PrometheusGraph {
       currentChart.append('circle')
         .attr('class', 'circle-metric')
         .attr('fill', currentGraphProps.line_color)
-        .attr('cx', currentTimeCoordinate)
+        .attr('cx', currentDeployXPos || currentTimeCoordinate)
         .attr('cy', currentGraphProps.yScale(currentData.value))
         .attr('r', this.commonGraphProperties.circle_radius_metric);
 
+      if (currentDeployXPos) return;
+
       // The little box with text
-      const rectTextMetric = currentChart.append('g')
-        .attr('class', 'rect-text-metric')
-        .attr('translate', `(${currentTimeCoordinate}, ${currentGraphProps.yScale(currentData.value)})`);
+      const rectTextMetric = currentChart.append('svg')
+        .attr({
+          class: 'rect-text-metric',
+          x: currentTimeCoordinate,
+          y: 0,
+        });
 
       rectTextMetric.append('rect')
-        .attr('class', 'rect-metric')
-        .attr('x', currentTimeCoordinate + 10)
-        .attr('y', maxMetricValue)
-        .attr('width', this.commonGraphProperties.rect_text_width)
-        .attr('height', this.commonGraphProperties.rect_text_height);
+        .attr({
+          class: 'rect-metric',
+          x: 4,
+          y: 1,
+          rx: 2,
+          width: this.commonGraphProperties.rect_text_width,
+          height: this.commonGraphProperties.rect_text_height,
+        });
 
       rectTextMetric.append('text')
-        .attr('class', 'text-metric')
-        .attr('x', currentTimeCoordinate + 35)
-        .attr('y', maxMetricValue + 35)
+        .attr({
+          class: 'text-metric text-metric-bold',
+          x: 8,
+          y: 35,
+        })
         .text(timeFormat(currentData.time));
 
       rectTextMetric.append('text')
-        .attr('class', 'text-metric-date')
-        .attr('x', currentTimeCoordinate + 15)
-        .attr('y', maxMetricValue + 15)
-        .text(dayFormat(currentData.time));
+        .attr({
+          class: 'text-metric-date',
+          x: 8,
+          y: 15,
+        })
+        .text(dateFormat(currentData.time));
 
       let currentMetricValue = formatRelevantDigits(currentData.value);
       if (key === 'cpu_values') {
diff --git a/app/assets/javascripts/users_select.js b/app/assets/javascripts/users_select.js
index 0344ce9ffb4276a01f5290bed72ab54d4c34da6a..68cf9ced3efbf7aa895d1ad9809c65a3d0332f55 100644
--- a/app/assets/javascripts/users_select.js
+++ b/app/assets/javascripts/users_select.js
@@ -30,7 +30,7 @@
       $els.each((function(_this) {
         return function(i, dropdown) {
           var options = {};
-          var $block, $collapsedSidebar, $dropdown, $loading, $selectbox, $value, abilityName, assignTo, assigneeTemplate, collapsedAssigneeTemplate, defaultLabel, firstUser, issueURL, selectedId, showAnyUser, showNullUser, showMenuAbove;
+          var $block, $collapsedSidebar, $dropdown, $loading, $selectbox, $value, abilityName, assignTo, assigneeTemplate, collapsedAssigneeTemplate, defaultLabel, defaultNullUser, firstUser, issueURL, selectedId, selectedIdDefault, showAnyUser, showNullUser, showMenuAbove;
           $dropdown = $(dropdown);
           options.projectId = $dropdown.data('project-id');
           options.groupId = $dropdown.data('group-id');
@@ -38,11 +38,11 @@
           options.todoFilter = $dropdown.data('todo-filter');
           options.todoStateFilter = $dropdown.data('todo-state-filter');
           showNullUser = $dropdown.data('null-user');
+          defaultNullUser = $dropdown.data('null-user-default');
           showMenuAbove = $dropdown.data('showMenuAbove');
           showAnyUser = $dropdown.data('any-user');
           firstUser = $dropdown.data('first-user');
           options.authorId = $dropdown.data('author-id');
-          selectedId = $dropdown.data('selected');
           defaultLabel = $dropdown.data('default-label');
           issueURL = $dropdown.data('issueUpdate');
           $selectbox = $dropdown.closest('.selectbox');
@@ -51,6 +51,8 @@
           $value = $block.find('.value');
           $collapsedSidebar = $block.find('.sidebar-collapsed-user');
           $loading = $block.find('.block-loading').fadeOut();
+          selectedIdDefault = (defaultNullUser && showNullUser) ? 0 : null;
+          selectedId = $dropdown.data('selected') || selectedIdDefault;
 
           var updateIssueBoardsIssue = function () {
             $loading.removeClass('hidden').fadeIn();
@@ -186,12 +188,14 @@
             fieldName: $dropdown.data('field-name'),
             toggleLabel: function(selected, el) {
               if (selected && 'id' in selected && $(el).hasClass('is-active')) {
+                $dropdown.find('.dropdown-toggle-text').removeClass('is-default');
                 if (selected.text) {
                   return selected.text;
                 } else {
                   return selected.name;
                 }
               } else {
+                $dropdown.find('.dropdown-toggle-text').addClass('is-default');
                 return defaultLabel;
               }
             },
@@ -204,13 +208,14 @@
             },
             vue: $dropdown.hasClass('js-issue-board-sidebar'),
             clicked: function(user, $el, e) {
-              var isIssueIndex, isMRIndex, page, selected;
+              var isIssueIndex, isMRIndex, page, selected, isSelecting;
               page = $('body').data('page');
               isIssueIndex = page === 'projects:issues:index';
               isMRIndex = (page === page && page === 'projects:merge_requests:index');
+              isSelecting = (user.id !== selectedId);
+              selectedId = isSelecting ? user.id : selectedIdDefault;
               if ($dropdown.hasClass('js-filter-bulk-update') || $dropdown.hasClass('js-issuable-form-dropdown')) {
                 e.preventDefault();
-                selectedId = user.id;
                 if (selectedId === gon.current_user_id) {
                   $('.assign-to-me-link').hide();
                 } else {
@@ -221,12 +226,11 @@
               if ($el.closest('.add-issues-modal').length) {
                 gl.issueBoards.ModalStore.store.filter[$dropdown.data('field-name')] = user.id;
               } else if ($dropdown.hasClass('js-filter-submit') && (isIssueIndex || isMRIndex)) {
-                selectedId = user.id;
                 return Issuable.filterResults($dropdown.closest('form'));
               } else if ($dropdown.hasClass('js-filter-submit')) {
                 return $dropdown.closest('form').submit();
               } else if ($dropdown.hasClass('js-issue-board-sidebar')) {
-                if (user.id) {
+                if (user.id && isSelecting) {
                   gl.issueBoards.boardStoreIssueSet('assignee', new ListUser({
                     id: user.id,
                     username: user.username,
@@ -248,6 +252,9 @@
             },
             opened: function(e) {
               const $el = $(e.currentTarget);
+              if ($dropdown.hasClass('js-issue-board-sidebar')) {
+                selectedId = parseInt($dropdown[0].dataset.selected, 10) || selectedIdDefault;
+              }
               $el.find('.is-active').removeClass('is-active');
               $el.find(`li[data-user-id="${selectedId}"] .dropdown-menu-user-link`).addClass('is-active');
             },
diff --git a/app/assets/stylesheets/framework/dropdowns.scss b/app/assets/stylesheets/framework/dropdowns.scss
index 1313ea25c2a20cc98b6f9548c92ae4251102e5be..73ded9f30d470347b68a29b3358cfa6aa04dfcf9 100644
--- a/app/assets/stylesheets/framework/dropdowns.scss
+++ b/app/assets/stylesheets/framework/dropdowns.scss
@@ -390,7 +390,8 @@
       &::before {
         position: absolute;
         left: 6px;
-        top: 6px;
+        top: 50%;
+        transform: translateY(-50%);
         font: normal normal normal 14px/1 FontAwesome;
         font-size: inherit;
         text-rendering: auto;
diff --git a/app/assets/stylesheets/pages/environments.scss b/app/assets/stylesheets/pages/environments.scss
index 72e7d42858dbb6a3e417bc6f81146bf9f2f6d91a..026d35295d79a2d40ce4ab04191056c26509f833 100644
--- a/app/assets/stylesheets/pages/environments.scss
+++ b/app/assets/stylesheets/pages/environments.scss
@@ -157,7 +157,8 @@
 
 .prometheus-graph {
   text {
-    fill: $stat-graph-axis-fill;
+    fill: $gl-text-color;
+    stroke-width: 0;
   }
 
   .label-axis-text,
@@ -210,27 +211,33 @@
 .rect-text-metric {
   fill: $white-light;
   stroke-width: 1;
-  stroke: $black;
+  stroke: $gray-darkest;
 }
 
 .rect-axis-text {
   fill: $white-light;
 }
 
-.text-metric,
-.text-median-metric,
-.text-metric-usage,
-.text-metric-date {
-  fill: $black;
+.text-metric {
+  font-weight: 600;
 }
 
-.text-metric-date {
-  font-weight: 200;
+.selected-metric-line {
+  stroke: $gl-gray-dark;
+  stroke-width: 1;
 }
 
-.selected-metric-line {
+.deployment-line {
   stroke: $black;
-  stroke-width: 1;
+  stroke-width: 2;
+}
+
+.deploy-info-text {
+  dominant-baseline: text-before-edge;
+}
+
+.text-metric-bold {
+  font-weight: 600;
 }
 
 .prometheus-state {
diff --git a/app/controllers/admin/hooks_controller.rb b/app/controllers/admin/hooks_controller.rb
index cbfc4581411031a7f81cdd01e9c4e43a345971a3..a119934febcf4d96dddd46adc7c1e147f2d141e2 100644
--- a/app/controllers/admin/hooks_controller.rb
+++ b/app/controllers/admin/hooks_controller.rb
@@ -1,4 +1,6 @@
 class Admin::HooksController < Admin::ApplicationController
+  before_action :hook, only: :edit
+
   def index
     @hooks = SystemHook.all
     @hook = SystemHook.new
@@ -15,15 +17,25 @@ class Admin::HooksController < Admin::ApplicationController
     end
   end
 
+  def edit
+  end
+
+  def update
+    if hook.update_attributes(hook_params)
+      flash[:notice] = 'System hook was successfully updated.'
+      redirect_to admin_hooks_path
+    else
+      render 'edit'
+    end
+  end
+
   def destroy
-    @hook = SystemHook.find(params[:id])
-    @hook.destroy
+    hook.destroy
 
     redirect_to admin_hooks_path
   end
 
   def test
-    @hook = SystemHook.find(params[:hook_id])
     data = {
       event_name: "project_create",
       name: "Ruby",
@@ -32,11 +44,17 @@ class Admin::HooksController < Admin::ApplicationController
       owner_name: "Someone",
       owner_email: "example@gitlabhq.com"
     }
-    @hook.execute(data, 'system_hooks')
+    hook.execute(data, 'system_hooks')
 
     redirect_back_or_default
   end
 
+  private
+
+  def hook
+    @hook ||= SystemHook.find(params[:id])
+  end
+
   def hook_params
     params.require(:hook).permit(
       :enable_ssl_verification,
diff --git a/app/controllers/concerns/notes_actions.rb b/app/controllers/concerns/notes_actions.rb
new file mode 100644
index 0000000000000000000000000000000000000000..c32038d07bfaa3e9e3b202645fe2fffe69d8b5b1
--- /dev/null
+++ b/app/controllers/concerns/notes_actions.rb
@@ -0,0 +1,136 @@
+module NotesActions
+  include RendersNotes
+  extend ActiveSupport::Concern
+
+  included do
+    before_action :authorize_admin_note!, only: [:update, :destroy]
+  end
+
+  def index
+    current_fetched_at = Time.now.to_i
+
+    notes_json = { notes: [], last_fetched_at: current_fetched_at }
+
+    @notes = notes_finder.execute.inc_relations_for_view
+    @notes = prepare_notes_for_rendering(@notes)
+
+    @notes.each do |note|
+      next if note.cross_reference_not_visible_for?(current_user)
+
+      notes_json[:notes] << note_json(note)
+    end
+
+    render json: notes_json
+  end
+
+  def create
+    create_params = note_params.merge(
+      merge_request_diff_head_sha: params[:merge_request_diff_head_sha],
+      in_reply_to_discussion_id: params[:in_reply_to_discussion_id]
+    )
+    @note = Notes::CreateService.new(project, current_user, create_params).execute
+
+    if @note.is_a?(Note)
+      Banzai::NoteRenderer.render([@note], @project, current_user)
+    end
+
+    respond_to do |format|
+      format.json { render json: note_json(@note) }
+      format.html { redirect_back_or_default }
+    end
+  end
+
+  def update
+    @note = Notes::UpdateService.new(project, current_user, note_params).execute(note)
+
+    if @note.is_a?(Note)
+      Banzai::NoteRenderer.render([@note], @project, current_user)
+    end
+
+    respond_to do |format|
+      format.json { render json: note_json(@note) }
+      format.html { redirect_back_or_default }
+    end
+  end
+
+  def destroy
+    if note.editable?
+      Notes::DestroyService.new(project, current_user).execute(note)
+    end
+
+    respond_to do |format|
+      format.js { head :ok }
+    end
+  end
+
+  private
+
+  def note_json(note)
+    attrs = {
+      commands_changes: note.commands_changes
+    }
+
+    if note.persisted?
+      attrs.merge!(
+        valid: true,
+        id: note.id,
+        discussion_id: note.discussion_id(noteable),
+        html: note_html(note),
+        note: note.note
+      )
+
+      discussion = note.to_discussion(noteable)
+      unless discussion.individual_note?
+        attrs.merge!(
+          discussion_resolvable: discussion.resolvable?,
+
+          diff_discussion_html: diff_discussion_html(discussion),
+          discussion_html: discussion_html(discussion)
+        )
+      end
+    else
+      attrs.merge!(
+        valid: false,
+        errors: note.errors
+      )
+    end
+
+    attrs
+  end
+
+  def authorize_admin_note!
+    return access_denied! unless can?(current_user, :admin_note, note)
+  end
+
+  def note_params
+    params.require(:note).permit(
+      :project_id,
+      :noteable_type,
+      :noteable_id,
+      :commit_id,
+      :noteable,
+      :type,
+
+      :note,
+      :attachment,
+
+      # LegacyDiffNote
+      :line_code,
+
+      # DiffNote
+      :position
+    )
+  end
+
+  def noteable
+    @noteable ||= notes_finder.target
+  end
+
+  def last_fetched_at
+    request.headers['X-Last-Fetched-At']
+  end
+
+  def notes_finder
+    @notes_finder ||= NotesFinder.new(project, current_user, finder_params)
+  end
+end
diff --git a/app/controllers/concerns/renders_notes.rb b/app/controllers/concerns/renders_notes.rb
index dd21066ac13a3b64ad0abf50e2edf8bf3e2d91b3..41c3114ad1e98ce3e91580fd5a441e269fbf4862 100644
--- a/app/controllers/concerns/renders_notes.rb
+++ b/app/controllers/concerns/renders_notes.rb
@@ -10,6 +10,8 @@ module RendersNotes
   private
 
   def preload_max_access_for_authors(notes, project)
+    return nil unless project
+
     user_ids = notes.map(&:author_id)
     project.team.max_member_access_for_user_ids(user_ids)
   end
diff --git a/app/controllers/concerns/snippets_actions.rb b/app/controllers/concerns/snippets_actions.rb
index ca6dffe1cc57ac1f755547d9adc163f0826579f0..ffea712a833a59c440115a306444a558b6c1aea4 100644
--- a/app/controllers/concerns/snippets_actions.rb
+++ b/app/controllers/concerns/snippets_actions.rb
@@ -5,10 +5,12 @@ module SnippetsActions
   end
 
   def raw
+    disposition = params[:inline] == 'false' ? 'attachment' : 'inline'
+
     send_data(
       convert_line_endings(@snippet.content),
       type: 'text/plain; charset=utf-8',
-      disposition: 'inline',
+      disposition: disposition,
       filename: @snippet.sanitized_file_name
     )
   end
diff --git a/app/controllers/concerns/toggle_award_emoji.rb b/app/controllers/concerns/toggle_award_emoji.rb
index fbf9a026b108e14b80ca844d03951b52e6e171f2..ba5b7d33f87e1d87356a89a2cb7821a19e300cb6 100644
--- a/app/controllers/concerns/toggle_award_emoji.rb
+++ b/app/controllers/concerns/toggle_award_emoji.rb
@@ -22,7 +22,8 @@ module ToggleAwardEmoji
   def to_todoable(awardable)
     case awardable
     when Note
-      awardable.noteable
+      # we don't create todos for personal snippet comments for now
+      awardable.for_personal_snippet? ? nil : awardable.noteable
     when MergeRequest, Issue
       awardable
     when Snippet
diff --git a/app/controllers/projects/deployments_controller.rb b/app/controllers/projects/deployments_controller.rb
new file mode 100644
index 0000000000000000000000000000000000000000..c319671456d0a24644fa2b4807a60251a51f19e5
--- /dev/null
+++ b/app/controllers/projects/deployments_controller.rb
@@ -0,0 +1,18 @@
+class Projects::DeploymentsController < Projects::ApplicationController
+  before_action :authorize_read_environment!
+  before_action :authorize_read_deployment!
+
+  def index
+    deployments = environment.deployments.reorder(created_at: :desc)
+    deployments = deployments.where('created_at > ?', params[:after].to_time) if params[:after]&.to_time
+
+    render json: { deployments: DeploymentSerializer.new(user: @current_user, project: project)
+                                  .represent_concise(deployments) }
+  end
+
+  private
+
+  def environment
+    @environment ||= project.environments.find(params[:environment_id])
+  end
+end
diff --git a/app/controllers/projects/hooks_controller.rb b/app/controllers/projects/hooks_controller.rb
index 1e41f980f3173267d6aa66c2ac4c00f5a940987a..86d13a0d2226a5e99cccc25bfd4e73a1c84f60de 100644
--- a/app/controllers/projects/hooks_controller.rb
+++ b/app/controllers/projects/hooks_controller.rb
@@ -1,6 +1,7 @@
 class Projects::HooksController < Projects::ApplicationController
   # Authorize
   before_action :authorize_admin_project!
+  before_action :hook, only: :edit
 
   respond_to :html
 
@@ -17,6 +18,18 @@ class Projects::HooksController < Projects::ApplicationController
     redirect_to namespace_project_settings_integrations_path(@project.namespace, @project)
   end
 
+  def edit
+  end
+
+  def update
+    if hook.update_attributes(hook_params)
+      flash[:notice] = 'Hook was successfully updated.'
+      redirect_to namespace_project_settings_integrations_path(@project.namespace, @project)
+    else
+      render 'edit'
+    end
+  end
+
   def test
     if !@project.empty_repo?
       status, message = TestHookService.new.execute(hook, current_user)
diff --git a/app/controllers/projects/notes_controller.rb b/app/controllers/projects/notes_controller.rb
index 405ea3c0a4f838eaf75bf9aeada630adc20352e4..37f51b2ebe3ba9d728b3a6eb258a92da048123c9 100644
--- a/app/controllers/projects/notes_controller.rb
+++ b/app/controllers/projects/notes_controller.rb
@@ -1,68 +1,22 @@
 class Projects::NotesController < Projects::ApplicationController
-  include RendersNotes
+  include NotesActions
   include ToggleAwardEmoji
 
-  # Authorize
   before_action :authorize_read_note!
   before_action :authorize_create_note!, only: [:create]
-  before_action :authorize_admin_note!, only: [:update, :destroy]
   before_action :authorize_resolve_note!, only: [:resolve, :unresolve]
 
-  def index
-    current_fetched_at = Time.now.to_i
-
-    notes_json = { notes: [], last_fetched_at: current_fetched_at }
-
-    @notes = notes_finder.execute.inc_relations_for_view
-    @notes = prepare_notes_for_rendering(@notes)
-
-    @notes.each do |note|
-      next if note.cross_reference_not_visible_for?(current_user)
-
-      notes_json[:notes] << note_json(note)
-    end
-
-    render json: notes_json
-  end
-
+  #
+  # This is a fix to make spinach feature tests passing:
+  # Controller actions are returned from AbstractController::Base and methods of parent classes are
+  #   excluded in order to return only specific controller related methods.
+  # That is ok for the app (no :create method in ancestors)
+  #   but fails for tests because there is a :create method on FactoryGirl (one of the ancestors)
+  #
+  # see https://github.com/rails/rails/blob/v4.2.7/actionpack/lib/abstract_controller/base.rb#L78
+  #
   def create
-    create_params = note_params.merge(
-      merge_request_diff_head_sha: params[:merge_request_diff_head_sha],
-      in_reply_to_discussion_id: params[:in_reply_to_discussion_id]
-    )
-    @note = Notes::CreateService.new(project, current_user, create_params).execute
-
-    if @note.is_a?(Note)
-      Banzai::NoteRenderer.render([@note], @project, current_user)
-    end
-
-    respond_to do |format|
-      format.json { render json: note_json(@note) }
-      format.html { redirect_back_or_default }
-    end
-  end
-
-  def update
-    @note = Notes::UpdateService.new(project, current_user, note_params).execute(note)
-
-    if @note.is_a?(Note)
-      Banzai::NoteRenderer.render([@note], @project, current_user)
-    end
-
-    respond_to do |format|
-      format.json { render json: note_json(@note) }
-      format.html { redirect_back_or_default }
-    end
-  end
-
-  def destroy
-    if note.editable?
-      Notes::DestroyService.new(project, current_user).execute(note)
-    end
-
-    respond_to do |format|
-      format.js { head :ok }
-    end
+    super
   end
 
   def delete_attachment
@@ -110,7 +64,7 @@ class Projects::NotesController < Projects::ApplicationController
 
   def note_html(note)
     render_to_string(
-      "projects/notes/_note",
+      "shared/notes/_note",
       layout: false,
       formats: [:html],
       locals: { note: note }
@@ -152,76 +106,11 @@ class Projects::NotesController < Projects::ApplicationController
     )
   end
 
-  def note_json(note)
-    attrs = {
-      commands_changes: note.commands_changes
-    }
-
-    if note.persisted?
-      attrs.merge!(
-        valid: true,
-        id: note.id,
-        discussion_id: note.discussion_id(noteable),
-        html: note_html(note),
-        note: note.note
-      )
-
-      discussion = note.to_discussion(noteable)
-      unless discussion.individual_note?
-        attrs.merge!(
-          discussion_resolvable: discussion.resolvable?,
-
-          diff_discussion_html: diff_discussion_html(discussion),
-          discussion_html: discussion_html(discussion)
-        )
-      end
-    else
-      attrs.merge!(
-        valid: false,
-        errors: note.errors
-      )
-    end
-
-    attrs
-  end
-
-  def authorize_admin_note!
-    return access_denied! unless can?(current_user, :admin_note, note)
+  def finder_params
+    params.merge(last_fetched_at: last_fetched_at)
   end
 
   def authorize_resolve_note!
     return access_denied! unless can?(current_user, :resolve_note, note)
   end
-
-  def note_params
-    params.require(:note).permit(
-      :project_id,
-      :noteable_type,
-      :noteable_id,
-      :commit_id,
-      :noteable,
-      :type,
-
-      :note,
-      :attachment,
-
-      # LegacyDiffNote
-      :line_code,
-
-      # DiffNote
-      :position
-    )
-  end
-
-  def notes_finder
-    @notes_finder ||= NotesFinder.new(project, current_user, params.merge(last_fetched_at: last_fetched_at))
-  end
-
-  def noteable
-    @noteable ||= notes_finder.target
-  end
-
-  def last_fetched_at
-    request.headers['X-Last-Fetched-At']
-  end
 end
diff --git a/app/controllers/snippets/notes_controller.rb b/app/controllers/snippets/notes_controller.rb
new file mode 100644
index 0000000000000000000000000000000000000000..3c4ddc1680de014a5ee311dadfa99d366b15a804
--- /dev/null
+++ b/app/controllers/snippets/notes_controller.rb
@@ -0,0 +1,44 @@
+class Snippets::NotesController < ApplicationController
+  include NotesActions
+  include ToggleAwardEmoji
+
+  skip_before_action :authenticate_user!, only: [:index]
+  before_action :snippet
+  before_action :authorize_read_snippet!, only: [:show, :index, :create]
+
+  private
+
+  def note
+    @note ||= snippet.notes.find(params[:id])
+  end
+  alias_method :awardable, :note
+
+  def note_html(note)
+    render_to_string(
+      "shared/notes/_note",
+      layout: false,
+      formats: [:html],
+      locals: { note: note }
+    )
+  end
+
+  def project
+    nil
+  end
+
+  def snippet
+    PersonalSnippet.find_by(id: params[:snippet_id])
+  end
+
+  def note_params
+    super.merge(noteable_id: params[:snippet_id])
+  end
+
+  def finder_params
+    params.merge(last_fetched_at: last_fetched_at, target_id: snippet.id, target_type: 'personal_snippet')
+  end
+
+  def authorize_read_snippet!
+    return render_404 unless can?(current_user, :read_personal_snippet, snippet)
+  end
+end
diff --git a/app/controllers/snippets_controller.rb b/app/controllers/snippets_controller.rb
index 906833505d1c5400283a36e110d9226706079de9..da1ae9a34d98020f5a83c7a64e50e175c0dda460 100644
--- a/app/controllers/snippets_controller.rb
+++ b/app/controllers/snippets_controller.rb
@@ -1,14 +1,15 @@
 class SnippetsController < ApplicationController
+  include RendersNotes
   include ToggleAwardEmoji
   include SpammableActions
   include SnippetsActions
   include MarkdownPreview
   include RendersBlob
 
-  before_action :snippet, only: [:show, :edit, :destroy, :update, :raw, :download]
+  before_action :snippet, only: [:show, :edit, :destroy, :update, :raw]
 
   # Allow read snippet
-  before_action :authorize_read_snippet!, only: [:show, :raw, :download]
+  before_action :authorize_read_snippet!, only: [:show, :raw]
 
   # Allow modify snippet
   before_action :authorize_update_snippet!, only: [:edit, :update]
@@ -16,7 +17,7 @@ class SnippetsController < ApplicationController
   # Allow destroy snippet
   before_action :authorize_admin_snippet!, only: [:destroy]
 
-  skip_before_action :authenticate_user!, only: [:index, :show, :raw, :download]
+  skip_before_action :authenticate_user!, only: [:index, :show, :raw]
 
   layout 'snippets'
   respond_to :html
@@ -64,6 +65,11 @@ class SnippetsController < ApplicationController
     blob = @snippet.blob
     override_max_blob_size(blob)
 
+    @noteable = @snippet
+
+    @discussions = @snippet.discussions
+    @notes = prepare_notes_for_rendering(@discussions.flat_map(&:notes))
+
     respond_to do |format|
       format.html do
         render 'show'
@@ -83,14 +89,6 @@ class SnippetsController < ApplicationController
     redirect_to snippets_path
   end
 
-  def download
-    send_data(
-      convert_line_endings(@snippet.content),
-      type: 'text/plain; charset=utf-8',
-      filename: @snippet.sanitized_file_name
-    )
-  end
-
   def preview_markdown
     render_markdown_preview(params[:text], skip_project_check: true)
   end
diff --git a/app/finders/notes_finder.rb b/app/finders/notes_finder.rb
index 3c499184b415533a28a11037a2479fbe90736d20..dc6a8ad1f6654f3a57e00ce4f6a37f31a2848609 100644
--- a/app/finders/notes_finder.rb
+++ b/app/finders/notes_finder.rb
@@ -68,6 +68,8 @@ class NotesFinder
       MergeRequestsFinder.new(@current_user, project_id: @project.id).execute
     when "snippet", "project_snippet"
       SnippetsFinder.new.execute(@current_user, filter: :by_project, project: @project)
+    when "personal_snippet"
+      PersonalSnippet.all
     else
       raise 'invalid target_type'
     end
diff --git a/app/helpers/award_emoji_helper.rb b/app/helpers/award_emoji_helper.rb
index 167b09e678f39f188a68177387516a3a81250109..024cf38469ec1e41c9b7d3971bab2c8df4804ec0 100644
--- a/app/helpers/award_emoji_helper.rb
+++ b/app/helpers/award_emoji_helper.rb
@@ -1,10 +1,14 @@
 module AwardEmojiHelper
   def toggle_award_url(awardable)
-    return url_for([:toggle_award_emoji, awardable]) unless @project
+    return url_for([:toggle_award_emoji, awardable]) unless @project || awardable.is_a?(Note)
 
     if awardable.is_a?(Note)
       # We render a list of notes very frequently and calling the specific method is a lot faster than the generic one (4.5x)
-      toggle_award_emoji_namespace_project_note_url(@project.namespace, @project, awardable.id)
+      if awardable.for_personal_snippet?
+        toggle_award_emoji_snippet_note_path(awardable.noteable, awardable)
+      else
+        toggle_award_emoji_namespace_project_note_path(@project.namespace, @project, awardable.id)
+      end
     else
       url_for([:toggle_award_emoji, @project.namespace.becomes(Namespace), @project, awardable])
     end
diff --git a/app/helpers/merge_requests_helper.rb b/app/helpers/merge_requests_helper.rb
index e347f61fb8dae1102be7726301bf749163b3072b..2614cdfe90e5ae3a6fbfc8ef3f2e8b93f4452e54 100644
--- a/app/helpers/merge_requests_helper.rb
+++ b/app/helpers/merge_requests_helper.rb
@@ -1,6 +1,6 @@
 module MergeRequestsHelper
   def new_mr_path_from_push_event(event)
-    target_project = event.project.forked_from_project || event.project
+    target_project = event.project.default_merge_request_target
     new_namespace_project_merge_request_path(
       event.project.namespace,
       event.project,
@@ -127,6 +127,10 @@ module MergeRequestsHelper
     end
   end
 
+  def target_projects(project)
+    [project, project.default_merge_request_target].uniq
+  end
+
   def merge_request_button_visibility(merge_request, closed)
     return 'hidden' if merge_request.closed? == closed || (merge_request.merged? == closed && !merge_request.closed?) || merge_request.closed_without_fork?
   end
diff --git a/app/helpers/snippets_helper.rb b/app/helpers/snippets_helper.rb
index 979264c94217c1b1ce4a9d33403af196c39941ec..2fd64b3441e4c33b9110f8034817c8c07a452c2e 100644
--- a/app/helpers/snippets_helper.rb
+++ b/app/helpers/snippets_helper.rb
@@ -8,6 +8,14 @@ module SnippetsHelper
     end
   end
 
+  def download_snippet_path(snippet)
+    if snippet.project_id
+      raw_namespace_project_snippet_path(@project.namespace, @project, snippet, inline: false)
+    else
+      raw_snippet_path(snippet, inline: false)
+    end
+  end
+
   # Return the path of a snippets index for a user or for a project
   #
   # @returns String, path to snippet index
diff --git a/app/models/concerns/cache_markdown_field.rb b/app/models/concerns/cache_markdown_field.rb
index f033028c4e53ea48e8e6d9ebaf6baf91619324ae..eb32bf3d32a74695ed748bf388a61289532ad84b 100644
--- a/app/models/concerns/cache_markdown_field.rb
+++ b/app/models/concerns/cache_markdown_field.rb
@@ -78,6 +78,9 @@ module CacheMarkdownField
   def cached_html_up_to_date?(markdown_field)
     html_field = cached_markdown_fields.html_field(markdown_field)
 
+    cached = !cached_html_for(markdown_field).nil? && !__send__(markdown_field).nil?
+    return false unless cached
+
     markdown_changed = attribute_changed?(markdown_field) || false
     html_changed = attribute_changed?(html_field) || false
 
diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb
index 9d2288c311e6784ecef2fd2c1e4fd3cb425bb2da..365fa4f1e7059c1f08c4a5bebfdd222c883c637e 100644
--- a/app/models/merge_request.rb
+++ b/app/models/merge_request.rb
@@ -100,6 +100,7 @@ class MergeRequest < ActiveRecord::Base
   validates :merge_user, presence: true, if: :merge_when_pipeline_succeeds?, unless: :importing?
   validate :validate_branches, unless: [:allow_broken, :importing?, :closed_without_fork?]
   validate :validate_fork, unless: :closed_without_fork?
+  validate :validate_target_project, on: :create
 
   scope :by_source_or_target_branch, ->(branch_name) do
     where("source_branch = :branch OR target_branch = :branch", branch: branch_name)
@@ -330,6 +331,12 @@ class MergeRequest < ActiveRecord::Base
     end
   end
 
+  def validate_target_project
+    return true if target_project.merge_requests_enabled?
+
+    errors.add :base, 'Target project has disabled merge requests'
+  end
+
   def validate_fork
     return true unless target_project && source_project
     return true if target_project == source_project
diff --git a/app/models/namespace.rb b/app/models/namespace.rb
index 9bfa731785f9053f1b84b0d262d19d6964710f9c..397dc7a25ab63b3f4a65ff5580440a5a7e6837f3 100644
--- a/app/models/namespace.rb
+++ b/app/models/namespace.rb
@@ -33,7 +33,7 @@ class Namespace < ActiveRecord::Base
   validates :path,
     presence: true,
     length: { maximum: 255 },
-    namespace: true
+    dynamic_path: true
 
   validate :nesting_level_allowed
 
@@ -220,6 +220,10 @@ class Namespace < ActiveRecord::Base
     Project.inside_path(full_path)
   end
 
+  def has_parent?
+    parent.present?
+  end
+
   private
 
   def repository_storage_paths
diff --git a/app/models/project.rb b/app/models/project.rb
index c7dc562c2387aa4c125fc9ecdc0645687019a915..025db89ebfd82f75e6c322c615d9b9dd2f77460c 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -196,13 +196,14 @@ class Project < ActiveRecord::Base
               message: Gitlab::Regex.project_name_regex_message }
   validates :path,
     presence: true,
-    project_path: true,
+    dynamic_path: true,
     length: { maximum: 255 },
     format: { with: Gitlab::Regex.project_path_regex,
-              message: Gitlab::Regex.project_path_regex_message }
+              message: Gitlab::Regex.project_path_regex_message },
+    uniqueness: { scope: :namespace_id }
+
   validates :namespace, presence: true
   validates :name, uniqueness: { scope: :namespace_id }
-  validates :path, uniqueness: { scope: :namespace_id }
   validates :import_url, addressable_url: true, if: :external_import?
   validates :import_url, importable_url: true, if: [:external_import?, :import_url_changed?]
   validates :star_count, numericality: { greater_than_or_equal_to: 0 }
@@ -1270,6 +1271,9 @@ class Project < ActiveRecord::Base
     else
       update_attribute(name, value)
     end
+
+  rescue ActiveRecord::RecordNotSaved => e
+    handle_update_attribute_error(e, value)
   end
 
   def pushes_since_gc
@@ -1314,6 +1318,14 @@ class Project < ActiveRecord::Base
     namespace_id_changed?
   end
 
+  def default_merge_request_target
+    if forked_from_project&.merge_requests_enabled?
+      forked_from_project
+    else
+      self
+    end
+  end
+
   alias_method :name_with_namespace, :full_name
   alias_method :human_name, :full_name
   alias_method :path_with_namespace, :full_path
@@ -1383,4 +1395,16 @@ class Project < ActiveRecord::Base
 
     ContainerRepository.build_root_repository(self).has_tags?
   end
+
+  def handle_update_attribute_error(ex, value)
+    if ex.message.start_with?('Failed to replace')
+      if value.respond_to?(:each)
+        invalid = value.detect(&:invalid?)
+
+        raise ex, ([ex.message] + invalid.errors.full_messages).join(' ') if invalid
+      end
+    end
+
+    raise ex
+  end
 end
diff --git a/app/models/repository.rb b/app/models/repository.rb
index feabfa111fb96245f471771328f75cdd307f1267..ba34d570dbd197e607fe3df10681b64838670c86 100644
--- a/app/models/repository.rb
+++ b/app/models/repository.rb
@@ -505,14 +505,8 @@ class Repository
   delegate :tag_names, to: :raw_repository
   cache_method :tag_names, fallback: []
 
-  def branch_count
-    branches.size
-  end
+  delegate :branch_count, :tag_count, to: :raw_repository
   cache_method :branch_count, fallback: 0
-
-  def tag_count
-    raw_repository.rugged.tags.count
-  end
   cache_method :tag_count, fallback: 0
 
   def avatar
diff --git a/app/models/user.rb b/app/models/user.rb
index bd9c9f99663819e15491c0ba97f51c66f27d4ac0..2b7ebe6c1a71fb545b57bf3931850323d0ef57d0 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -118,7 +118,7 @@ class User < ActiveRecord::Base
     presence: true,
     numericality: { greater_than_or_equal_to: 0, less_than_or_equal_to: Gitlab::Database::MAX_INT_VALUE }
   validates :username,
-    namespace: true,
+    dynamic_path: true,
     presence: true,
     uniqueness: { case_sensitive: false }
 
diff --git a/app/serializers/deployment_entity.rb b/app/serializers/deployment_entity.rb
index d610fbe0c8ac51d26edd9e042119cebd9298b951..8b3de1bed0f2f6039ac125571794f509ad90db2f 100644
--- a/app/serializers/deployment_entity.rb
+++ b/app/serializers/deployment_entity.rb
@@ -18,8 +18,10 @@ class DeploymentEntity < Grape::Entity
     end
   end
 
+  expose :created_at
   expose :tag
   expose :last?
+
   expose :user, using: UserEntity
   expose :commit, using: CommitEntity
   expose :deployable, using: BuildEntity
diff --git a/app/serializers/deployment_serializer.rb b/app/serializers/deployment_serializer.rb
new file mode 100644
index 0000000000000000000000000000000000000000..cba5c3f311f77f9ad3e5a84bedb7381639b86fcc
--- /dev/null
+++ b/app/serializers/deployment_serializer.rb
@@ -0,0 +1,8 @@
+class DeploymentSerializer < BaseSerializer
+  entity DeploymentEntity
+
+  def represent_concise(resource, opts = {})
+    opts[:only] = [:iid, :id, :sha, :created_at, :tag, :last?, :id, ref: [:name]]
+    represent(resource, opts)
+  end
+end
diff --git a/app/serializers/status_entity.rb b/app/serializers/status_entity.rb
index 944472f3e51d2a601ae42df7bebdfbbfd63cc31f..188c3747f184f3bce39438677b5250be1280e842 100644
--- a/app/serializers/status_entity.rb
+++ b/app/serializers/status_entity.rb
@@ -7,6 +7,9 @@ class StatusEntity < Grape::Entity
   expose :details_path
 
   expose :favicon do |status|
-    ActionController::Base.helpers.image_path(File.join('ci_favicons', "#{status.favicon}.ico"))
+    dir = 'ci_favicons'
+    dir = File.join(dir, 'dev') if Rails.env.development?
+
+    ActionController::Base.helpers.image_path(File.join(dir, "#{status.favicon}.ico"))
   end
 end
diff --git a/app/services/merge_requests/build_service.rb b/app/services/merge_requests/build_service.rb
index d45da5180e10c17115bb278f4f99a1bb00e26acd..bc0e7ad4e39a7f789b3b7b5bb2f3eb2d3d5ce5a8 100644
--- a/app/services/merge_requests/build_service.rb
+++ b/app/services/merge_requests/build_service.rb
@@ -28,7 +28,7 @@ module MergeRequests
 
     def find_target_project
       return target_project if target_project.present? && can?(current_user, :read_project, target_project)
-      project.forked_from_project || project
+      project.default_merge_request_target
     end
 
     def find_target_branch
diff --git a/app/services/todo_service.rb b/app/services/todo_service.rb
index b6e88b0280f228b2ebe4dc2f527ba3f63b785a94..8ae61694b503e12fad35dd1245bd68db2d47a572 100644
--- a/app/services/todo_service.rb
+++ b/app/services/todo_service.rb
@@ -281,7 +281,7 @@ class TodoService
 
   def attributes_for_target(target)
     attributes = {
-      project_id: target.project.id,
+      project_id: target&.project&.id,
       target_id: target.id,
       target_type: target.class.name,
       commit_id: nil
diff --git a/app/validators/dynamic_path_validator.rb b/app/validators/dynamic_path_validator.rb
new file mode 100644
index 0000000000000000000000000000000000000000..226eb6b313c1f047b8431c5647cb44d0f11977c9
--- /dev/null
+++ b/app/validators/dynamic_path_validator.rb
@@ -0,0 +1,208 @@
+# DynamicPathValidator
+#
+# Custom validator for GitLab path values.
+# These paths are assigned to `Namespace` (& `Group` as a subclass) & `Project`
+#
+# Values are checked for formatting and exclusion from a list of reserved path
+# names.
+class DynamicPathValidator < ActiveModel::EachValidator
+  # All routes that appear on the top level must be listed here.
+  # This will make sure that groups cannot be created with these names
+  # as these routes would be masked by the paths already in place.
+  #
+  # Example:
+  #   /api/api-project
+  #
+  #  the path `api` shouldn't be allowed because it would be masked by `api/*`
+  #
+  TOP_LEVEL_ROUTES = %w[
+    -
+    .well-known
+    abuse_reports
+    admin
+    all
+    api
+    assets
+    autocomplete
+    ci
+    dashboard
+    explore
+    files
+    groups
+    health_check
+    help
+    hooks
+    import
+    invites
+    issues
+    jwt
+    koding
+    member
+    merge_requests
+    new
+    notes
+    notification_settings
+    oauth
+    profile
+    projects
+    public
+    repository
+    robots.txt
+    s
+    search
+    sent_notifications
+    services
+    snippets
+    teams
+    u
+    unicorn_test
+    unsubscribes
+    uploads
+    users
+  ].freeze
+
+  # This list should contain all words following `/*namespace_id/:project_id` in
+  # routes that contain a second wildcard.
+  #
+  # Example:
+  #   /*namespace_id/:project_id/badges/*ref/build
+  #
+  # If `badges` was allowed as a project/group name, we would not be able to access the
+  # `badges` route for those projects:
+  #
+  # Consider a namespace with path `foo/bar` and a project called `badges`.
+  # The route to the build badge would then be `/foo/bar/badges/badges/master/build.svg`
+  #
+  # When accessing this path the route would be matched to the `badges` path
+  # with the following params:
+  #   - namespace_id: `foo`
+  #   - project_id: `bar`
+  #   - ref: `badges/master`
+  #
+  # Failing to find the project, this would result in a 404.
+  #
+  # By rejecting `badges` the router can _count_ on the fact that `badges` will
+  # be preceded by the `namespace/project`.
+  WILDCARD_ROUTES = %w[
+    badges
+    blame
+    blob
+    builds
+    commits
+    create
+    create_dir
+    edit
+    environments/folders
+    files
+    find_file
+    gitlab-lfs/objects
+    info/lfs/objects
+    new
+    preview
+    raw
+    refs
+    tree
+    update
+    wikis
+  ].freeze
+
+  # These are all the paths that follow `/groups/*id/ or `/groups/*group_id`
+  # We need to reject these because we have a `/groups/*id` page that is the same
+  # as the `/*id`.
+  #
+  # If we would allow a subgroup to be created with the name `activity` then
+  # this group would not be accessible through `/groups/parent/activity` since
+  # this would map to the activity-page of it's parent.
+  GROUP_ROUTES = %w[
+    activity
+    avatar
+    edit
+    group_members
+    issues
+    labels
+    merge_requests
+    milestones
+    projects
+    subgroups
+  ].freeze
+
+  CHILD_ROUTES = (WILDCARD_ROUTES | GROUP_ROUTES).freeze
+
+  def self.without_reserved_wildcard_paths_regex
+    @without_reserved_wildcard_paths_regex ||= regex_excluding_child_paths(WILDCARD_ROUTES)
+  end
+
+  def self.without_reserved_child_paths_regex
+    @without_reserved_child_paths_regex ||= regex_excluding_child_paths(CHILD_ROUTES)
+  end
+
+  # This is used to validate a full path.
+  # It doesn't match paths
+  #   - Starting with one of the top level words
+  #   - Containing one of the child level words in the middle of a path
+  def self.regex_excluding_child_paths(child_routes)
+    reserved_top_level_words = Regexp.union(TOP_LEVEL_ROUTES)
+    not_starting_in_reserved_word = %r{\A/?(?!(#{reserved_top_level_words})(/|\z))}
+
+    reserved_child_level_words = Regexp.union(child_routes)
+    not_containing_reserved_child = %r{(?!\S+/(#{reserved_child_level_words})(/|\z))}
+
+    %r{#{not_starting_in_reserved_word}
+       #{not_containing_reserved_child}
+       #{Gitlab::Regex.full_namespace_regex}}x
+  end
+
+  def self.valid?(path)
+    path =~ Gitlab::Regex.full_namespace_regex && !full_path_reserved?(path)
+  end
+
+  def self.full_path_reserved?(path)
+    path = path.to_s.downcase
+    _project_part, namespace_parts = path.reverse.split('/', 2).map(&:reverse)
+
+    wildcard_reserved?(path) || child_reserved?(namespace_parts)
+  end
+
+  def self.child_reserved?(path)
+    return false unless path
+
+    path !~ without_reserved_child_paths_regex
+  end
+
+  def self.wildcard_reserved?(path)
+    return false unless path
+
+    path !~ without_reserved_wildcard_paths_regex
+  end
+
+  delegate :full_path_reserved?,
+           :child_reserved?,
+           to: :class
+
+  def path_reserved_for_record?(record, value)
+    full_path = record.respond_to?(:full_path) ? record.full_path : value
+
+    # For group paths the entire path cannot contain a reserved child word
+    # The path doesn't contain the last `_project_part` so we need to validate
+    # if the entire path.
+    # Example:
+    #   A *group* with full path `parent/activity` is reserved.
+    #   A *project* with full path `parent/activity` is allowed.
+    if record.is_a? Group
+      child_reserved?(full_path)
+    else
+      full_path_reserved?(full_path)
+    end
+  end
+
+  def validate_each(record, attribute, value)
+    unless value =~ Gitlab::Regex.namespace_regex
+      record.errors.add(attribute, Gitlab::Regex.namespace_regex_message)
+      return
+    end
+
+    if path_reserved_for_record?(record, value)
+      record.errors.add(attribute, "#{value} is a reserved name")
+    end
+  end
+end
diff --git a/app/validators/namespace_validator.rb b/app/validators/namespace_validator.rb
deleted file mode 100644
index 77ca033e97f70aed54321fe4f081aeea365d6c76..0000000000000000000000000000000000000000
--- a/app/validators/namespace_validator.rb
+++ /dev/null
@@ -1,73 +0,0 @@
-# NamespaceValidator
-#
-# Custom validator for GitLab namespace values.
-#
-# Values are checked for formatting and exclusion from a list of reserved path
-# names.
-class NamespaceValidator < ActiveModel::EachValidator
-  RESERVED = %w[
-    .well-known
-    admin
-    all
-    assets
-    ci
-    dashboard
-    files
-    groups
-    help
-    hooks
-    issues
-    merge_requests
-    new
-    notes
-    profile
-    projects
-    public
-    repository
-    robots.txt
-    s
-    search
-    services
-    snippets
-    teams
-    u
-    unsubscribes
-    users
-  ].freeze
-
-  WILDCARD_ROUTES = %w[tree commits wikis new edit create update logs_tree
-                       preview blob blame raw files create_dir find_file
-                       artifacts graphs refs badges].freeze
-
-  STRICT_RESERVED = (RESERVED + WILDCARD_ROUTES).freeze
-
-  def self.valid?(value)
-    !reserved?(value) && follow_format?(value)
-  end
-
-  def self.reserved?(value, strict: false)
-    if strict
-      STRICT_RESERVED.include?(value)
-    else
-      RESERVED.include?(value)
-    end
-  end
-
-  def self.follow_format?(value)
-    value =~ Gitlab::Regex.namespace_regex
-  end
-
-  delegate :reserved?, :follow_format?, to: :class
-
-  def validate_each(record, attribute, value)
-    unless follow_format?(value)
-      record.errors.add(attribute, Gitlab::Regex.namespace_regex_message)
-    end
-
-    strict = record.is_a?(Group) && record.parent_id
-
-    if reserved?(value, strict: strict)
-      record.errors.add(attribute, "#{value} is a reserved name")
-    end
-  end
-end
diff --git a/app/validators/project_path_validator.rb b/app/validators/project_path_validator.rb
deleted file mode 100644
index ee2ae65be7bc6f1b4872409158c0c96d346d94df..0000000000000000000000000000000000000000
--- a/app/validators/project_path_validator.rb
+++ /dev/null
@@ -1,35 +0,0 @@
-# ProjectPathValidator
-#
-# Custom validator for GitLab project path values.
-#
-# Values are checked for formatting and exclusion from a list of reserved path
-# names.
-class ProjectPathValidator < ActiveModel::EachValidator
-  # All project routes with wildcard argument must be listed here.
-  # Otherwise it can lead to routing issues when route considered as project name.
-  #
-  # Example:
-  #  /group/project/tree/deploy_keys
-  #
-  #  without tree as reserved name routing can match 'group/project' as group name,
-  #  'tree' as project name and 'deploy_keys' as route.
-  #
-  RESERVED = (NamespaceValidator::STRICT_RESERVED -
-              %w[dashboard help ci admin search notes services assets profile public]).freeze
-
-  def self.valid?(value)
-    !reserved?(value)
-  end
-
-  def self.reserved?(value)
-    RESERVED.include?(value)
-  end
-
-  delegate :reserved?, to: :class
-
-  def validate_each(record, attribute, value)
-    if reserved?(value)
-      record.errors.add(attribute, "#{value} is a reserved name")
-    end
-  end
-end
diff --git a/app/views/admin/dashboard/index.html.haml b/app/views/admin/dashboard/index.html.haml
index 8c9fdc9ae4246b95a848cd3319e169323394b7b3..53f0a1e7fdeb4fedcf1a16092171ca3f2017158a 100644
--- a/app/views/admin/dashboard/index.html.haml
+++ b/app/views/admin/dashboard/index.html.haml
@@ -73,6 +73,12 @@
           = container_reg
           %span.light.pull-right
             = boolean_to_icon Gitlab.config.registry.enabled
+        - gitlab_pages = 'GitLab Pages'
+        - gitlab_pages_enabled = Gitlab.config.pages.enabled
+        %p{ "aria-label" => "#{gitlab_pages}: status " + (gitlab_pages_enabled ? "on" : "off") }
+          = gitlab_pages
+          %span.light.pull-right
+            = boolean_to_icon gitlab_pages_enabled
 
       .col-md-4
         %h4
diff --git a/app/views/admin/hooks/_form.html.haml b/app/views/admin/hooks/_form.html.haml
new file mode 100644
index 0000000000000000000000000000000000000000..6217d5fb1351b80988722a5bc0e89d1e19a5cdc4
--- /dev/null
+++ b/app/views/admin/hooks/_form.html.haml
@@ -0,0 +1,40 @@
+= form_errors(hook)
+
+.form-group
+  = form.label :url, 'URL', class: 'control-label'
+  .col-sm-10
+    = form.text_field :url, class: 'form-control'
+.form-group
+  = form.label :token, 'Secret Token', class: 'control-label'
+  .col-sm-10
+    = form.text_field :token, class: 'form-control'
+    %p.help-block
+      Use this token to validate received payloads
+.form-group
+  = form.label :url, 'Trigger', class: 'control-label'
+  .col-sm-10.prepend-top-10
+    %div
+      System hook will be triggered on set of events like creating project
+      or adding ssh key. But you can also enable extra triggers like Push events.
+
+    .prepend-top-default
+      = form.check_box :push_events, class: 'pull-left'
+      .prepend-left-20
+        = form.label :push_events, class: 'list-label' do
+          %strong Push events
+        %p.light
+          This url will be triggered by a push to the repository
+    %div
+      = form.check_box :tag_push_events, class: 'pull-left'
+      .prepend-left-20
+        = form.label :tag_push_events, class: 'list-label' do
+          %strong Tag push events
+        %p.light
+          This url will be triggered when a new tag is pushed to the repository
+.form-group
+  = form.label :enable_ssl_verification, 'SSL verification', class: 'control-label checkbox'
+  .col-sm-10
+    .checkbox
+      = form.label :enable_ssl_verification do
+        = form.check_box :enable_ssl_verification
+        %strong Enable SSL verification
diff --git a/app/views/admin/hooks/edit.html.haml b/app/views/admin/hooks/edit.html.haml
new file mode 100644
index 0000000000000000000000000000000000000000..0777f5e262971656f1409735fffcfde6748d43db
--- /dev/null
+++ b/app/views/admin/hooks/edit.html.haml
@@ -0,0 +1,14 @@
+- page_title 'Edit System Hook'
+%h3.page-title
+  Edit System Hook
+
+%p.light
+  #{link_to 'System hooks ', help_page_path('system_hooks/system_hooks'), class: 'vlink'} can be
+  used for binding events when GitLab creates a User or Project.
+
+%hr
+
+= form_for @hook, as: :hook, url: admin_hook_path, html: { class: 'form-horizontal' } do |f|
+  = render partial: 'form', locals: { form: f, hook: @hook }
+  .form-actions
+    = f.submit 'Save changes', class: 'btn btn-create'
diff --git a/app/views/admin/hooks/index.html.haml b/app/views/admin/hooks/index.html.haml
index d9c7948763a13dd024ac99e6d42a9dc480ff889c..71117758921228ea5537b740ed7d676450a1839e 100644
--- a/app/views/admin/hooks/index.html.haml
+++ b/app/views/admin/hooks/index.html.haml
@@ -1,57 +1,17 @@
-- page_title "System Hooks"
+- page_title 'System Hooks'
 %h3.page-title
   System hooks
 
 %p.light
-  #{link_to "System hooks ", help_page_path("system_hooks/system_hooks"), class: "vlink"} can be
+  #{link_to 'System hooks ', help_page_path('system_hooks/system_hooks'), class: 'vlink'} can be
   used for binding events when GitLab creates a User or Project.
 
 %hr
 
-
 = form_for @hook, as: :hook, url: admin_hooks_path, html: { class: 'form-horizontal' } do |f|
-  = form_errors(@hook)
-
-  .form-group
-    = f.label :url, 'URL', class: 'control-label'
-    .col-sm-10
-      = f.text_field :url, class: 'form-control'
-  .form-group
-    = f.label :token, 'Secret Token', class: 'control-label'
-    .col-sm-10
-      = f.text_field :token, class: 'form-control'
-      %p.help-block
-        Use this token to validate received payloads
-  .form-group
-    = f.label :url, "Trigger", class: 'control-label'
-    .col-sm-10.prepend-top-10
-      %div
-        System hook will be triggered on set of events like creating project
-        or adding ssh key. But you can also enable extra triggers like Push events.
-
-      .prepend-top-default
-        = f.check_box :push_events, class: 'pull-left'
-        .prepend-left-20
-          = f.label :push_events, class: 'list-label' do
-            %strong Push events
-          %p.light
-            This url will be triggered by a push to the repository
-      %div
-        = f.check_box :tag_push_events, class: 'pull-left'
-        .prepend-left-20
-          = f.label :tag_push_events, class: 'list-label' do
-            %strong Tag push events
-          %p.light
-            This url will be triggered when a new tag is pushed to the repository
-  .form-group
-    = f.label :enable_ssl_verification, "SSL verification", class: 'control-label checkbox'
-    .col-sm-10
-      .checkbox
-        = f.label :enable_ssl_verification do
-          = f.check_box :enable_ssl_verification
-          %strong Enable SSL verification
+  = render partial: 'form', locals: { form: f, hook: @hook }
   .form-actions
-    = f.submit "Add system hook", class: "btn btn-create"
+    = f.submit 'Add system hook', class: 'btn btn-create'
 %hr
 
 - if @hooks.any?
@@ -62,11 +22,12 @@
       - @hooks.each do |hook|
         %li
           .controls
-            = link_to 'Test hook', admin_hook_test_path(hook), class: "btn btn-sm"
-            = link_to 'Remove', admin_hook_path(hook), data: { confirm: 'Are you sure?' }, method: :delete, class: "btn btn-remove btn-sm"
+            = link_to 'Test hook', test_admin_hook_path(hook), class: 'btn btn-sm'
+            = link_to 'Edit', edit_admin_hook_path(hook), class: 'btn btn-sm'
+            = link_to 'Remove', admin_hook_path(hook), data: { confirm: 'Are you sure?' }, method: :delete, class: 'btn btn-remove btn-sm'
           .monospace= hook.url
           %div
             - %w(push_events tag_push_events issues_events note_events merge_requests_events build_events).each do |trigger|
               - if hook.send(trigger)
                 %span.label.label-gray= trigger.titleize
-            %span.label.label-gray SSL Verification: #{hook.enable_ssl_verification ? "enabled" : "disabled"}
+            %span.label.label-gray SSL Verification: #{hook.enable_ssl_verification ? 'enabled' : 'disabled'}
diff --git a/app/views/discussions/_notes.html.haml b/app/views/discussions/_notes.html.haml
index 34789808f102793cca99fcb2d66943e6cbff8b22..964473ee3e08c0023fbb4ae56fe7c8f9522b0791 100644
--- a/app/views/discussions/_notes.html.haml
+++ b/app/views/discussions/_notes.html.haml
@@ -1,6 +1,6 @@
 .discussion-notes
   %ul.notes{ data: { discussion_id: discussion.id } }
-    = render partial: "projects/notes/note", collection: discussion.notes, as: :note
+    = render partial: "shared/notes/note", collection: discussion.notes, as: :note
 
   - if current_user
     .discussion-reply-holder
diff --git a/app/views/projects/boards/components/sidebar/_assignee.html.haml b/app/views/projects/boards/components/sidebar/_assignee.html.haml
index e75ce305440c19776b8ecd2db676294308e14d4f..0f42433452134a94152b8ced5f0ef38577981db8 100644
--- a/app/views/projects/boards/components/sidebar/_assignee.html.haml
+++ b/app/views/projects/boards/components/sidebar/_assignee.html.haml
@@ -28,8 +28,9 @@
         ":value" => "issue.assignee.id",
         "v-if" => "issue.assignee" }
       .dropdown
-        %button.dropdown-menu-toggle.js-user-search.js-author-search.js-issue-board-sidebar{ type: "button", data: { toggle: "dropdown", field_name: "issue[assignee_id]", first_user: (current_user.username if current_user), current_user: "true", project_id: @project.id, null_user: "true" },
+        %button.dropdown-menu-toggle.js-user-search.js-author-search.js-issue-board-sidebar{ type: "button", data: { toggle: "dropdown", field_name: "issue[assignee_id]", first_user: (current_user.username if current_user), current_user: "true", project_id: @project.id, null_user: "true", null_user_default: "true" },
           ":data-issuable-id" => "issue.id",
+          ":data-selected" => "assigneeId",
           ":data-issue-update" => "'#{namespace_project_issues_path(@project.namespace, @project)}/' + issue.id + '.json'" }
           Select assignee
           = icon("chevron-down")
diff --git a/app/views/projects/empty.html.haml b/app/views/projects/empty.html.haml
index 85e442e115c423a40a583ef35a930286adef760a..50e0bad3ccf6051cd51c6b40c31eedb6187e44b9 100644
--- a/app/views/projects/empty.html.haml
+++ b/app/views/projects/empty.html.haml
@@ -60,7 +60,7 @@
               git init
               git remote add origin #{ content_tag(:span, default_url_to_repo, class: 'clone')}
               git add .
-              git commit
+              git commit -m "Initial commit"
               git push -u origin master
 
         %fieldset
diff --git a/app/views/projects/environments/metrics.html.haml b/app/views/projects/environments/metrics.html.haml
index 766f119116fe7f64a1d6368082b6f504122d1c12..e8f8fbbcf092594ecb4b8a672538bb3abe54aee2 100644
--- a/app/views/projects/environments/metrics.html.haml
+++ b/app/views/projects/environments/metrics.html.haml
@@ -5,7 +5,7 @@
   = page_specific_javascript_bundle_tag('monitoring')
 = render "projects/pipelines/head"
 
-.prometheus-container{ class: container_class, 'data-has-metrics': "#{@environment.has_metrics?}" }
+#js-metrics.prometheus-container{ class: container_class, data: { has_metrics: "#{@environment.has_metrics?}", deployment_endpoint: namespace_project_environment_deployments_path(@project.namespace, @project, @environment, format: :json) } }
   .top-area
     .row
       .col-sm-6
diff --git a/app/views/projects/hooks/_index.html.haml b/app/views/projects/hooks/_index.html.haml
index 8faad351463a84a94516bbd2c09d2e84c8d815e7..676b7c345bca61f5b025679503d7a9a33785a046 100644
--- a/app/views/projects/hooks/_index.html.haml
+++ b/app/views/projects/hooks/_index.html.haml
@@ -1 +1,23 @@
-= render 'shared/web_hooks/form', hook: @hook, hooks: @hooks, url_components: [@project.namespace.becomes(Namespace), @project]
+.row.prepend-top-default
+  .col-lg-3
+    %h4.prepend-top-0
+      = page_title
+    %p
+      #{link_to 'Webhooks', help_page_path('user/project/integrations/webhooks')} can be
+      used for binding events when something is happening within the project.
+
+  .col-lg-9.append-bottom-default
+    = form_for @hook, as: :hook, url: polymorphic_path([@project.namespace.becomes(Namespace), @project, :hooks]) do |f|
+      = render partial: 'shared/web_hooks/form', locals: { form: f, hook: @hook }
+      = f.submit 'Add webhook', class: 'btn btn-create'
+
+    %hr
+    %h5.prepend-top-default
+      Webhooks (#{@hooks.count})
+    - if @hooks.any?
+      %ul.well-list
+        - @hooks.each do |hook|
+          = render 'project_hook', hook: hook
+    - else
+      %p.settings-message.text-center.append-bottom-0
+        No webhooks found, add one in the form above.
diff --git a/app/views/projects/hooks/edit.html.haml b/app/views/projects/hooks/edit.html.haml
new file mode 100644
index 0000000000000000000000000000000000000000..7998713be1f23e98df7a6f14d2a159c4c42365d7
--- /dev/null
+++ b/app/views/projects/hooks/edit.html.haml
@@ -0,0 +1,14 @@
+= render 'projects/settings/head'
+
+.row.prepend-top-default
+  .col-lg-3
+    %h4.prepend-top-0
+      = page_title
+    %p
+      #{link_to 'Webhooks', help_page_path('user/project/integrations/webhooks')} can be
+      used for binding events when something is happening within the project.
+  .col-lg-9.append-bottom-default
+    = form_for [@project.namespace.becomes(Namespace), @project, @hook], as: :hook, url: namespace_project_hook_path do |f|
+      = render partial: 'shared/web_hooks/form', locals: { form: f, hook: @hook }
+      = f.submit 'Save changes', class: 'btn btn-create'
+
diff --git a/app/views/projects/merge_requests/_new_compare.html.haml b/app/views/projects/merge_requests/_new_compare.html.haml
index 8d134aaac671b065598db54fda128c1b4739778a..9cf24e1084214ef6291d3f40b0484906e09fe17b 100644
--- a/app/views/projects/merge_requests/_new_compare.html.haml
+++ b/app/views/projects/merge_requests/_new_compare.html.haml
@@ -38,7 +38,7 @@
         .panel-heading
           Target branch
         .panel-body.clearfix
-          - projects =  @project.forked_from_project.nil? ? [@project] : [@project, @project.forked_from_project]
+          - projects = target_projects(@project)
           .merge-request-select.dropdown
             = f.hidden_field :target_project_id
             = dropdown_toggle f.object.target_project.path_with_namespace, { toggle: "dropdown", field_name: "#{f.object_name}[target_project_id]", disabled: @merge_request.persisted? }, { toggle_class: "js-compare-dropdown js-target-project" }
diff --git a/app/views/projects/notes/_actions.html.haml b/app/views/projects/notes/_actions.html.haml
new file mode 100644
index 0000000000000000000000000000000000000000..718b52dd82e818e47db53b3268d6ad30f7d1e7ba
--- /dev/null
+++ b/app/views/projects/notes/_actions.html.haml
@@ -0,0 +1,44 @@
+- access = note_max_access_for_user(note)
+- if access
+  %span.note-role= access
+
+- if note.resolvable?
+  - can_resolve = can?(current_user, :resolve_note, note)
+  %resolve-btn{ "project-path" => project_path(note.project),
+      "discussion-id" => note.discussion_id(@noteable),
+      ":note-id" => note.id,
+      ":resolved" => note.resolved?,
+      ":can-resolve" => can_resolve,
+      ":author-name" => "'#{j(note.author.name)}'",
+      "author-avatar" => note.author.avatar_url,
+      ":note-truncated" => "'#{j(truncate(note.note, length: 17))}'",
+      ":resolved-by" => "'#{j(note.resolved_by.try(:name))}'",
+      "v-show" => "#{can_resolve || note.resolved?}",
+      "inline-template" => true,
+      "ref" => "note_#{note.id}" }
+
+    %button.note-action-button.line-resolve-btn{ type: "button",
+        class: ("is-disabled" unless can_resolve),
+        ":class" => "{ 'is-active': isResolved }",
+        ":aria-label" => "buttonText",
+        "@click" => "resolve",
+        ":title" => "buttonText",
+        ":ref" => "'button'" }
+
+      = icon('spin spinner', 'v-show' => 'loading', class: 'loading', 'aria-hidden' => 'true', 'aria-label' => 'Loading')
+      %div{ 'v-show' => '!loading' }= render 'shared/icons/icon_status_success.svg'
+
+- if current_user
+  - if note.emoji_awardable?
+    - user_authored = note.user_authored?(current_user)
+    = link_to '#', title: 'Award Emoji', class: "note-action-button note-emoji-button js-add-award js-note-emoji #{'js-user-authored' if user_authored}", data: { position: 'right' } do
+      = icon('spinner spin')
+      %span{ class: 'link-highlight award-control-icon-neutral' }= custom_icon('emoji_slightly_smiling_face')
+      %span{ class: 'link-highlight award-control-icon-positive' }= custom_icon('emoji_smiley')
+      %span{ class: 'link-highlight award-control-icon-super-positive' }= custom_icon('emoji_smile')
+
+  - if note_editable
+    = link_to '#', title: 'Edit comment', class: 'note-action-button js-note-edit' do
+      = icon('pencil', class: 'link-highlight')
+    = link_to namespace_project_note_path(note.project.namespace, note.project, note), title: 'Remove comment', method: :delete, data: { confirm: 'Are you sure you want to remove this comment?' }, remote: true, class: 'note-action-button js-note-delete danger' do
+      = icon('trash-o', class: 'danger-highlight')
diff --git a/app/views/projects/notes/_edit.html.haml b/app/views/projects/notes/_edit.html.haml
new file mode 100644
index 0000000000000000000000000000000000000000..f1e251d65b75b99888ba3ffe0c58a66ad352b227
--- /dev/null
+++ b/app/views/projects/notes/_edit.html.haml
@@ -0,0 +1,3 @@
+.original-note-content.hidden{ data: { post_url: namespace_project_note_path(@project.namespace, @project, note), target_id: note.noteable.id, target_type: note.noteable.class.name.underscore } }
+  #{note.note}
+%textarea.hidden.js-task-list-field.original-task-list{ data: {update_url: namespace_project_note_path(@project.namespace, @project, note) } }= note.note
diff --git a/app/views/projects/notes/_note.html.haml b/app/views/projects/notes/_note.html.haml
deleted file mode 100644
index 7afccb3900ac91df0b35d98b859decf2711e5e7e..0000000000000000000000000000000000000000
--- a/app/views/projects/notes/_note.html.haml
+++ /dev/null
@@ -1,101 +0,0 @@
-- return unless note.author
-- return if note.cross_reference_not_visible_for?(current_user)
-
-- note_editable = note_editable?(note)
-%li.timeline-entry{ id: dom_id(note), class: ["note", "note-row-#{note.id}", ('system-note' if note.system)], data: {author_id: note.author.id, editable: note_editable, note_id: note.id} }
-  .timeline-entry-inner
-    .timeline-icon
-      - if note.system
-        = icon_for_system_note(note)
-      - else
-        %a{ href: user_path(note.author) }
-          = image_tag avatar_icon(note.author), alt: '', class: 'avatar s40'
-    .timeline-content
-      .note-header
-        .note-header-info
-          %a{ href: user_path(note.author) }
-            %span.hidden-xs
-              = sanitize(note.author.name)
-            %span.note-headline-light
-              = note.author.to_reference
-          %span.note-headline-light
-            %span.note-headline-meta
-              - unless note.system
-                commented
-              - if note.system
-                %span.system-note-message
-                  = note.redacted_note_html
-              %a{ href: "##{dom_id(note)}" }
-                = time_ago_with_tooltip(note.created_at, placement: 'bottom', html_class: 'note-created-ago')
-        - unless note.system?
-          .note-actions
-            - access = note_max_access_for_user(note)
-            - if access
-              %span.note-role= access
-
-            - if note.resolvable?
-              - can_resolve = can?(current_user, :resolve_note, note)
-              %resolve-btn{ "project-path" => project_path(note.project),
-                  "discussion-id" => note.discussion_id(@noteable),
-                  ":note-id" => note.id,
-                  ":resolved" => note.resolved?,
-                  ":can-resolve" => can_resolve,
-                  ":author-name" => "'#{j(note.author.name)}'",
-                  "author-avatar" => note.author.avatar_url,
-                  ":note-truncated" => "'#{j(truncate(note.note, length: 17))}'",
-                  ":resolved-by" => "'#{j(note.resolved_by.try(:name))}'",
-                  "v-show" => "#{can_resolve || note.resolved?}",
-                  "inline-template" => true,
-                  "ref" => "note_#{note.id}" }
-
-                %button.note-action-button.line-resolve-btn{ type: "button",
-                    class: ("is-disabled" unless can_resolve),
-                    ":class" => "{ 'is-active': isResolved }",
-                    ":aria-label" => "buttonText",
-                    "@click" => "resolve",
-                    ":title" => "buttonText",
-                    ":ref" => "'button'" }
-
-                  = icon("spin spinner", "v-show" => "loading", class: 'loading')
-                  %div{ 'v-show' => '!loading' }= render "shared/icons/icon_status_success.svg"
-
-            - if current_user
-              - if note.emoji_awardable?
-                - user_authored = note.user_authored?(current_user)
-                = link_to '#', title: 'Award Emoji', class: "note-action-button note-emoji-button js-add-award js-note-emoji #{'js-user-authored' if user_authored}", data: { position: 'right' } do
-                  = icon('spinner spin')
-                  %span{ class: "link-highlight award-control-icon-neutral" }= custom_icon('emoji_slightly_smiling_face')
-                  %span{ class: "link-highlight award-control-icon-positive" }= custom_icon('emoji_smiley')
-                  %span{ class: "link-highlight award-control-icon-super-positive" }= custom_icon('emoji_smile')
-
-              - if note_editable
-                = link_to '#', title: 'Edit comment', class: 'note-action-button js-note-edit' do
-                  = icon('pencil', class: 'link-highlight')
-                = link_to namespace_project_note_path(note.project.namespace, note.project, note), title: 'Remove comment', method: :delete, data: { confirm: 'Are you sure you want to remove this comment?' }, remote: true, class: 'note-action-button js-note-delete danger' do
-                  = icon('trash-o', class: 'danger-highlight')
-      .note-body{ class: note_editable ? 'js-task-list-container' : '' }
-        .note-text.md
-          = note.redacted_note_html
-        = edited_time_ago_with_tooltip(note, placement: 'bottom', html_class: 'note_edited_ago', include_author: true)
-        - if note_editable
-          .original-note-content.hidden{ data: { post_url: namespace_project_note_path(@project.namespace, @project, note), target_id: note.noteable.id, target_type: note.noteable.class.name.underscore } }
-            #{note.note}
-          %textarea.hidden.js-task-list-field.original-task-list{ data: {update_url: namespace_project_note_path(@project.namespace, @project, note) } }= note.note
-        .note-awards
-          = render 'award_emoji/awards_block', awardable: note, inline: false
-        - if note.system
-          .system-note-commit-list-toggler
-            Toggle commit list
-            %i.fa.fa-angle-down
-      - if note.attachment.url
-        .note-attachment
-          - if note.attachment.image?
-            = link_to note.attachment.url, target: '_blank' do
-              = image_tag note.attachment.url, class: 'note-image-attach'
-          .attachment
-            = link_to note.attachment.url, target: '_blank' do
-              = icon('paperclip')
-              = note.attachment_identifier
-              = link_to delete_attachment_namespace_project_note_path(note.project.namespace, note.project, note),
-                title: 'Delete this attachment', method: :delete, remote: true, data: { confirm: 'Are you sure you want to remove the attachment?' }, class: 'danger js-note-attachment-delete' do
-                = icon('trash-o', class: 'cred')
diff --git a/app/views/projects/notes/_notes_with_form.html.haml b/app/views/projects/notes/_notes_with_form.html.haml
index 90a150aa74c7941f8e486a7606dc0ebb8d122673..555228623cc6bb9d912cea75c28aed0e47d67857 100644
--- a/app/views/projects/notes/_notes_with_form.html.haml
+++ b/app/views/projects/notes/_notes_with_form.html.haml
@@ -1,5 +1,5 @@
 %ul#notes-list.notes.main-notes-list.timeline
-  = render "projects/notes/notes"
+  = render "shared/notes/notes"
 
 = render 'projects/notes/edit_form'
 
diff --git a/app/views/projects/settings/_head.html.haml b/app/views/projects/settings/_head.html.haml
index e50a543ffa845bcbf8fcd4fa4b88d289f2ba9766..5a5ade0362493f1758cca388960e8bf34ff14732 100644
--- a/app/views/projects/settings/_head.html.haml
+++ b/app/views/projects/settings/_head.html.haml
@@ -14,7 +14,7 @@
             %span
               Members
         - if can_edit
-          = nav_link(controller: [:integrations, :services]) do
+          = nav_link(controller: [:integrations, :services, :hooks]) do
             = link_to project_settings_integrations_path(@project), title: 'Integrations' do
               %span
                 Integrations
diff --git a/app/views/projects/settings/integrations/_project_hook.html.haml b/app/views/projects/settings/integrations/_project_hook.html.haml
index ceabe2eab3d50002cddbaf17e94b5c90e273f15c..8dc276a3becf49325f4570fbb6ca507d51e64ae4 100644
--- a/app/views/projects/settings/integrations/_project_hook.html.haml
+++ b/app/views/projects/settings/integrations/_project_hook.html.haml
@@ -9,6 +9,7 @@
     .col-md-4.col-lg-5.text-right-lg.prepend-top-5
       %span.append-right-10.inline
         SSL Verification: #{hook.enable_ssl_verification ? "enabled" : "disabled"}
+      = link_to "Edit", edit_namespace_project_hook_path(@project.namespace, @project, hook), class: "btn btn-sm"
       = link_to "Test", test_namespace_project_hook_path(@project.namespace, @project, hook), class: "btn btn-sm"
       = link_to namespace_project_hook_path(@project.namespace, @project, hook), data: { confirm: 'Are you sure?'}, method: :delete, class: "btn btn-transparent" do
         %span.sr-only Remove
diff --git a/app/views/shared/issuable/_search_bar.html.haml b/app/views/shared/issuable/_search_bar.html.haml
index f1350169bbed498350f25e0b578b564274f8be89..b6fce5e3cd439c16fc4770daea1257a75ffe56e7 100644
--- a/app/views/shared/issuable/_search_bar.html.haml
+++ b/app/views/shared/issuable/_search_bar.html.haml
@@ -117,7 +117,7 @@
       .issues_bulk_update.hide
         = form_tag [:bulk_update, @project.namespace.becomes(Namespace), @project, type], method: :post, class: 'bulk-update'  do
           .filter-item.inline
-            = dropdown_tag("Status", options: { toggle_class: "js-issue-status", title: "Change status", dropdown_class: "dropdown-menu-status dropdown-menu-selectable", data: { field_name: "update[state_event]" } } ) do
+            = dropdown_tag("Status", options: { toggle_class: "js-issue-status", title: "Change status", dropdown_class: "dropdown-menu-status dropdown-menu-selectable", data: { field_name: "update[state_event]", default_label: "Status" } } ) do
               %ul
                 %li
                   %a{ href: "#", data: { id: "reopen" } } Open
@@ -125,13 +125,13 @@
                   %a{ href: "#", data: { id: "close" } } Closed
           .filter-item.inline
             = dropdown_tag("Assignee", options: { toggle_class: "js-user-search js-update-assignee js-filter-submit js-filter-bulk-update", title: "Assign to", filter: true, dropdown_class: "dropdown-menu-user dropdown-menu-selectable",
-              placeholder: "Search authors", data: { first_user: (current_user.username if current_user), null_user: true, current_user: true, project_id: @project.id, field_name: "update[assignee_id]" } })
+              placeholder: "Search authors", data: { first_user: (current_user.username if current_user), null_user: true, current_user: true, project_id: @project.id, field_name: "update[assignee_id]", default_label: "Assignee" } })
           .filter-item.inline
-            = dropdown_tag("Milestone", options: { title: "Assign milestone", toggle_class: 'js-milestone-select js-extra-options js-filter-submit js-filter-bulk-update', filter: true, dropdown_class: "dropdown-menu-selectable dropdown-menu-milestone", placeholder: "Search milestones", data: { show_no: true, field_name: "update[milestone_id]", project_id: @project.id, milestones: namespace_project_milestones_path(@project.namespace, @project, :json), use_id: true } })
+            = dropdown_tag("Milestone", options: { title: "Assign milestone", toggle_class: 'js-milestone-select js-extra-options js-filter-submit js-filter-bulk-update', filter: true, dropdown_class: "dropdown-menu-selectable dropdown-menu-milestone", placeholder: "Search milestones", data: { show_no: true, field_name: "update[milestone_id]", project_id: @project.id, milestones: namespace_project_milestones_path(@project.namespace, @project, :json), use_id: true, default_label: "Milestone" } })
           .filter-item.inline.labels-filter
-            = render "shared/issuable/label_dropdown", classes: ['js-filter-bulk-update', 'js-multiselect'], dropdown_title: 'Apply a label', show_create: false, show_footer: false, extra_options: false, filter_submit: false, data_options: { persist_when_hide: "true", field_name: "update[label_ids][]", show_no: false, show_any: false, use_id: true }
+            = render "shared/issuable/label_dropdown", classes: ['js-filter-bulk-update', 'js-multiselect'], dropdown_title: 'Apply a label', show_create: false, show_footer: false, extra_options: false, filter_submit: false, data_options: { persist_when_hide: "true", field_name: "update[label_ids][]", show_no: false, show_any: false, use_id: true, default_label: "Labels" }
           .filter-item.inline
-            = dropdown_tag("Subscription", options: { toggle_class: "js-subscription-event", title: "Change subscription", dropdown_class: "dropdown-menu-selectable", data: { field_name: "update[subscription_event]" } } ) do
+            = dropdown_tag("Subscription", options: { toggle_class: "js-subscription-event", title: "Change subscription", dropdown_class: "dropdown-menu-selectable", data: { field_name: "update[subscription_event]", default_label: "Subscription" } } ) do
               %ul
                 %li
                   %a{ href: "#", data: { id: "subscribe" } } Subscribe
diff --git a/app/views/shared/issuable/_sidebar.html.haml b/app/views/shared/issuable/_sidebar.html.haml
index 2e0d6a129fb2539e61cbc53556ea51b7c62055fd..45065ac59207c81256095f1167014819ae93b876 100644
--- a/app/views/shared/issuable/_sidebar.html.haml
+++ b/app/views/shared/issuable/_sidebar.html.haml
@@ -48,7 +48,7 @@
 
         .selectbox.hide-collapsed
           = f.hidden_field 'assignee_id', value: issuable.assignee_id, id: 'issue_assignee_id'
-          = dropdown_tag('Select assignee', options: { toggle_class: 'js-user-search js-author-search', title: 'Assign to', filter: true, dropdown_class: 'dropdown-menu-user dropdown-menu-selectable dropdown-menu-author', placeholder: 'Search users', data: { first_user: (current_user.username if current_user), current_user: true, project_id: (@project.id if @project), author_id: issuable.author_id, field_name: "#{issuable.to_ability_name}[assignee_id]", issue_update: issuable_json_path(issuable), ability_name: issuable.to_ability_name, null_user: true } })
+          = dropdown_tag('Select assignee', options: { toggle_class: 'js-user-search js-author-search', title: 'Assign to', filter: true, dropdown_class: 'dropdown-menu-user dropdown-menu-selectable dropdown-menu-author', placeholder: 'Search users', data: { first_user: (current_user.username if current_user), current_user: true, project_id: (@project.id if @project), author_id: issuable.author_id, field_name: "#{issuable.to_ability_name}[assignee_id]", issue_update: issuable_json_path(issuable), ability_name: issuable.to_ability_name, null_user: true, null_user_default: true, selected: issuable.assignee_id } })
 
       .block.milestone
         .sidebar-collapsed-icon
diff --git a/app/views/shared/notes/_note.html.haml b/app/views/shared/notes/_note.html.haml
new file mode 100644
index 0000000000000000000000000000000000000000..731270d41272e15bd90a238b70d15e212f4f4422
--- /dev/null
+++ b/app/views/shared/notes/_note.html.haml
@@ -0,0 +1,62 @@
+- return unless note.author
+- return if note.cross_reference_not_visible_for?(current_user)
+
+- note_editable = note_editable?(note)
+%li.timeline-entry{ id: dom_id(note), class: ["note", "note-row-#{note.id}", ('system-note' if note.system)], data: {author_id: note.author.id, editable: note_editable, note_id: note.id} }
+  .timeline-entry-inner
+    .timeline-icon
+      - if note.system
+        = icon_for_system_note(note)
+      - else
+        %a{ href: user_path(note.author) }
+          = image_tag avatar_icon(note.author), alt: '', class: 'avatar s40'
+    .timeline-content
+      .note-header
+        .note-header-info
+          %a{ href: user_path(note.author) }
+            %span.hidden-xs
+              = sanitize(note.author.name)
+            %span.note-headline-light
+              = note.author.to_reference
+          %span.note-headline-light
+            %span.note-headline-meta
+              - unless note.system
+                commented
+              - if note.system
+                %span.system-note-message
+                  = note.redacted_note_html
+              %a{ href: "##{dom_id(note)}" }
+                = time_ago_with_tooltip(note.created_at, placement: 'bottom', html_class: 'note-created-ago')
+        - unless note.system?
+          .note-actions
+            - if note.for_personal_snippet?
+              = render 'snippets/notes/actions', note: note, note_editable: note_editable
+            - else
+              = render 'projects/notes/actions', note: note, note_editable: note_editable
+      .note-body{ class: note_editable ? 'js-task-list-container' : '' }
+        .note-text.md
+          = note.redacted_note_html
+        = edited_time_ago_with_tooltip(note, placement: 'bottom', html_class: 'note_edited_ago', include_author: true)
+        - if note_editable
+          - if note.for_personal_snippet?
+            = render 'snippets/notes/edit', note: note
+          - else
+            = render 'projects/notes/edit', note: note
+        .note-awards
+          = render 'award_emoji/awards_block', awardable: note, inline: false
+        - if note.system
+          .system-note-commit-list-toggler
+            Toggle commit list
+            %i.fa.fa-angle-down
+      - if note.attachment.url
+        .note-attachment
+          - if note.attachment.image?
+            = link_to note.attachment.url, target: '_blank' do
+              = image_tag note.attachment.url, class: 'note-image-attach'
+          .attachment
+            = link_to note.attachment.url, target: '_blank' do
+              = icon('paperclip')
+              = note.attachment_identifier
+              = link_to delete_attachment_namespace_project_note_path(note.project.namespace, note.project, note),
+                title: 'Delete this attachment', method: :delete, remote: true, data: { confirm: 'Are you sure you want to remove the attachment?' }, class: 'danger js-note-attachment-delete' do
+                = icon('trash-o', class: 'cred')
diff --git a/app/views/projects/notes/_notes.html.haml b/app/views/shared/notes/_notes.html.haml
similarity index 53%
rename from app/views/projects/notes/_notes.html.haml
rename to app/views/shared/notes/_notes.html.haml
index 2b2bab09c74f9c2b937c32c9c3dd52054800b4ed..cfdfeeb9e972f8e03542549d4c07df19ada35333 100644
--- a/app/views/projects/notes/_notes.html.haml
+++ b/app/views/shared/notes/_notes.html.haml
@@ -1,8 +1,8 @@
 - if defined?(@discussions)
   - @discussions.each do |discussion|
     - if discussion.individual_note?
-      = render partial: "projects/notes/note", collection: discussion.notes, as: :note
+      = render partial: "shared/notes/note", collection: discussion.notes, as: :note
     - else
       = render 'discussions/discussion', discussion: discussion
 - else
-  = render partial: "projects/notes/note", collection: @notes, as: :note
+  = render partial: "shared/notes/note", collection: @notes, as: :note
diff --git a/app/views/shared/snippets/_blob.html.haml b/app/views/shared/snippets/_blob.html.haml
index 67d186e2874a4ccc5cc9875981f6cbbd7b25e6e8..9bcb4544b975d11e1fffbd452304637ac398f409 100644
--- a/app/views/shared/snippets/_blob.html.haml
+++ b/app/views/shared/snippets/_blob.html.haml
@@ -18,7 +18,6 @@
       = copy_blob_source_button(blob)
       = open_raw_blob_button(blob)
 
-      - if defined?(download_path) && download_path
-        = link_to icon('download'), download_path, class: "btn btn-sm has-tooltip", title: 'Download', data: { container: 'body' }
+      = link_to icon('download'), download_snippet_path(@snippet), target: '_blank', class: "btn btn-sm has-tooltip", title: 'Download', data: { container: 'body' }
 
 = render 'projects/blob/content', blob: blob
diff --git a/app/views/shared/web_hooks/_form.html.haml b/app/views/shared/web_hooks/_form.html.haml
index ee3be3c789af39561856460d39ac408dac905b99..37c3e61912caf435bc5e633435005d818bc4f93b 100644
--- a/app/views/shared/web_hooks/_form.html.haml
+++ b/app/views/shared/web_hooks/_form.html.haml
@@ -1,102 +1,82 @@
-.row.prepend-top-default
-  .col-lg-3
-    %h4.prepend-top-0
-      = page_title
-    %p
-      #{link_to "Webhooks", help_page_path("user/project/integrations/webhooks")} can be
-      used for binding events when something is happening within the project.
-  .col-lg-9.append-bottom-default
-    = form_for hook, as: :hook, url: polymorphic_path(url_components + [:hooks]) do |f|
-      = form_errors(hook)
+= form_errors(hook)
 
-      .form-group
-        = f.label :url, "URL", class: 'label-light'
-        = f.text_field :url, class: "form-control", placeholder: 'http://example.com/trigger-ci.json'
-      .form-group
-        = f.label :token, "Secret Token", class: 'label-light'
-        = f.text_field :token, class: "form-control", placeholder: ''
-        %p.help-block
-          Use this token to validate received payloads. It will be sent with the request in the X-Gitlab-Token HTTP header.
-      .form-group
-        = f.label :url, "Trigger", class: 'label-light'
-        %ul.list-unstyled
-          %li
-            = f.check_box :push_events, class: 'pull-left'
-            .prepend-left-20
-              = f.label :push_events, class: 'list-label' do
-                %strong Push events
-              %p.light
-                This URL will be triggered by a push to the repository
-          %li
-            = f.check_box :tag_push_events, class: 'pull-left'
-            .prepend-left-20
-              = f.label :tag_push_events, class: 'list-label' do
-                %strong Tag push events
-              %p.light
-                This URL will be triggered when a new tag is pushed to the repository
-          %li
-            = f.check_box :note_events, class: 'pull-left'
-            .prepend-left-20
-              = f.label :note_events, class: 'list-label' do
-                %strong Comments
-              %p.light
-                This URL will be triggered when someone adds a comment
-          %li
-            = f.check_box :issues_events, class: 'pull-left'
-            .prepend-left-20
-              = f.label :issues_events, class: 'list-label' do
-                %strong Issues events
-              %p.light
-                This URL will be triggered when an issue is created/updated/merged
-          %li
-            = f.check_box :confidential_issues_events, class: 'pull-left'
-            .prepend-left-20
-              = f.label :confidential_issues_events, class: 'list-label' do
-                %strong Confidential Issues events
-              %p.light
-                This URL will be triggered when a confidential issue is created/updated/merged
-          %li
-            = f.check_box :merge_requests_events, class: 'pull-left'
-            .prepend-left-20
-              = f.label :merge_requests_events, class: 'list-label' do
-                %strong Merge Request events
-              %p.light
-                This URL will be triggered when a merge request is created/updated/merged
-          %li
-            = f.check_box :build_events, class: 'pull-left'
-            .prepend-left-20
-              = f.label :build_events, class: 'list-label' do
-                %strong Jobs events
-              %p.light
-                This URL will be triggered when the job status changes
-          %li
-            = f.check_box :pipeline_events, class: 'pull-left'
-            .prepend-left-20
-              = f.label :pipeline_events, class: 'list-label' do
-                %strong Pipeline events
-              %p.light
-                This URL will be triggered when the pipeline status changes
-          %li
-            = f.check_box :wiki_page_events, class: 'pull-left'
-            .prepend-left-20
-              = f.label :wiki_page_events, class: 'list-label' do
-                %strong Wiki Page events
-              %p.light
-                This URL will be triggered when a wiki page is created/updated
-      .form-group
-        = f.label :enable_ssl_verification, "SSL verification", class: 'label-light checkbox'
-        .checkbox
-          = f.label :enable_ssl_verification do
-            = f.check_box :enable_ssl_verification
-            %strong Enable SSL verification
-      = f.submit "Add webhook", class: "btn btn-create"
-    %hr
-    %h5.prepend-top-default
-      Webhooks (#{hooks.count})
-    - if hooks.any?
-      %ul.well-list
-        - hooks.each do |hook|
-          = render "project_hook", hook: hook
-    - else
-      %p.settings-message.text-center.append-bottom-0
-        No webhooks found, add one in the form above.
+.form-group
+  = form.label :url, 'URL', class: 'label-light'
+  = form.text_field :url, class: 'form-control', placeholder: 'http://example.com/trigger-ci.json'
+.form-group
+  = form.label :token, 'Secret Token', class: 'label-light'
+  = form.text_field :token, class: 'form-control', placeholder: ''
+  %p.help-block
+    Use this token to validate received payloads. It will be sent with the request in the X-Gitlab-Token HTTP header.
+.form-group
+  = form.label :url, 'Trigger', class: 'label-light'
+  %ul.list-unstyled
+    %li
+      = form.check_box :push_events, class: 'pull-left'
+      .prepend-left-20
+        = form.label :push_events, class: 'list-label' do
+          %strong Push events
+        %p.light
+          This URL will be triggered by a push to the repository
+    %li
+      = form.check_box :tag_push_events, class: 'pull-left'
+      .prepend-left-20
+        = form.label :tag_push_events, class: 'list-label' do
+          %strong Tag push events
+        %p.light
+          This URL will be triggered when a new tag is pushed to the repository
+    %li
+      = form.check_box :note_events, class: 'pull-left'
+      .prepend-left-20
+        = form.label :note_events, class: 'list-label' do
+          %strong Comments
+        %p.light
+          This URL will be triggered when someone adds a comment
+    %li
+      = form.check_box :issues_events, class: 'pull-left'
+      .prepend-left-20
+        = form.label :issues_events, class: 'list-label' do
+          %strong Issues events
+        %p.light
+          This URL will be triggered when an issue is created/updated/merged
+    %li
+      = form.check_box :confidential_issues_events, class: 'pull-left'
+      .prepend-left-20
+        = form.label :confidential_issues_events, class: 'list-label' do
+          %strong Confidential Issues events
+        %p.light
+          This URL will be triggered when a confidential issue is created/updated/merged
+    %li
+      = form.check_box :merge_requests_events, class: 'pull-left'
+      .prepend-left-20
+        = form.label :merge_requests_events, class: 'list-label' do
+          %strong Merge Request events
+        %p.light
+          This URL will be triggered when a merge request is created/updated/merged
+    %li
+      = form.check_box :build_events, class: 'pull-left'
+      .prepend-left-20
+        = form.label :build_events, class: 'list-label' do
+          %strong Jobs events
+        %p.light
+          This URL will be triggered when the job status changes
+    %li
+      = form.check_box :pipeline_events, class: 'pull-left'
+      .prepend-left-20
+        = form.label :pipeline_events, class: 'list-label' do
+          %strong Pipeline events
+        %p.light
+          This URL will be triggered when the pipeline status changes
+    %li
+      = form.check_box :wiki_page_events, class: 'pull-left'
+      .prepend-left-20
+        = form.label :wiki_page_events, class: 'list-label' do
+          %strong Wiki Page events
+        %p.light
+          This URL will be triggered when a wiki page is created/updated
+.form-group
+  = form.label :enable_ssl_verification, 'SSL verification', class: 'label-light checkbox'
+  .checkbox
+    = form.label :enable_ssl_verification do
+      = form.check_box :enable_ssl_verification
+      %strong Enable SSL verification
diff --git a/app/views/snippets/notes/_actions.html.haml b/app/views/snippets/notes/_actions.html.haml
new file mode 100644
index 0000000000000000000000000000000000000000..dace11e5474613924efa827d4177adbc0b79a740
--- /dev/null
+++ b/app/views/snippets/notes/_actions.html.haml
@@ -0,0 +1,13 @@
+- if current_user
+  - if note.emoji_awardable?
+    - user_authored = note.user_authored?(current_user)
+    = link_to '#', title: 'Award Emoji', class: "note-action-button note-emoji-button js-add-award js-note-emoji #{'js-user-authored' if user_authored}", data: { position: 'right' } do
+      = icon('spinner spin')
+      %span{ class: 'link-highlight award-control-icon-neutral' }= custom_icon('emoji_slightly_smiling_face')
+      %span{ class: 'link-highlight award-control-icon-positive' }= custom_icon('emoji_smiley')
+      %span{ class: 'link-highlight award-control-icon-super-positive' }= custom_icon('emoji_smile')
+  - if note_editable
+    = link_to '#', title: 'Edit comment', class: 'note-action-button js-note-edit' do
+      = icon('pencil', class: 'link-highlight')
+    = link_to snippet_note_path(note.noteable, note), title: 'Remove comment', method: :delete, data: { confirm: 'Are you sure you want to remove this comment?' }, remote: true, class: 'note-action-button js-note-delete danger' do
+      = icon('trash-o', class: 'danger-highlight')
diff --git a/app/views/snippets/notes/_edit.html.haml b/app/views/snippets/notes/_edit.html.haml
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/app/views/snippets/notes/_notes.html.haml b/app/views/snippets/notes/_notes.html.haml
new file mode 100644
index 0000000000000000000000000000000000000000..f07d6b8c1266b31399a8b86efe2e5877efeac5dd
--- /dev/null
+++ b/app/views/snippets/notes/_notes.html.haml
@@ -0,0 +1,2 @@
+%ul#notes-list.notes.main-notes-list.timeline
+  = render "projects/notes/notes"
diff --git a/app/views/snippets/show.html.haml b/app/views/snippets/show.html.haml
index 8a80013bbfd210661c8f544861eb2c5ae730a099..98287cba5b47c2e57b6df129d48fa1b8422f7fef 100644
--- a/app/views/snippets/show.html.haml
+++ b/app/views/snippets/show.html.haml
@@ -3,7 +3,10 @@
 = render 'shared/snippets/header'
 
 %article.file-holder.snippet-file-content
-  = render 'shared/snippets/blob', download_path: download_snippet_path(@snippet)
+  = render 'shared/snippets/blob'
 
 .row-content-block.top-block.content-component-block
   = render 'award_emoji/awards_block', awardable: @snippet, inline: true
+
+%ul#notes-list.notes.main-notes-list.timeline
+  #notes= render 'shared/notes/notes'
diff --git a/changelogs/unreleased/12910-personal-snippets-notes-show.yml b/changelogs/unreleased/12910-personal-snippets-notes-show.yml
new file mode 100644
index 0000000000000000000000000000000000000000..15c6f3c5e6aefebd82b5ab1edbc95885008b6ac1
--- /dev/null
+++ b/changelogs/unreleased/12910-personal-snippets-notes-show.yml
@@ -0,0 +1,4 @@
+---
+title: Display comments for personal snippets
+merge_request:
+author:
diff --git a/changelogs/unreleased/19364-webhook-edit.yml b/changelogs/unreleased/19364-webhook-edit.yml
new file mode 100644
index 0000000000000000000000000000000000000000..60e154b8b83e6dff3e89d076513a54d6e6ece8c9
--- /dev/null
+++ b/changelogs/unreleased/19364-webhook-edit.yml
@@ -0,0 +1,4 @@
+---
+title: Implement ability to edit hooks
+merge_request: 10816
+author: Alexander Randa
diff --git a/changelogs/unreleased/26488-target-disabled-mr.yml b/changelogs/unreleased/26488-target-disabled-mr.yml
new file mode 100644
index 0000000000000000000000000000000000000000..02058481ccf169b900041df545ebef999f09b72d
--- /dev/null
+++ b/changelogs/unreleased/26488-target-disabled-mr.yml
@@ -0,0 +1,4 @@
+---
+title: Disallow merge requests from fork when source project have disabled merge requests
+merge_request:
+author: mhasbini
diff --git a/changelogs/unreleased/30272-bvl-reject-more-namespaces.yml b/changelogs/unreleased/30272-bvl-reject-more-namespaces.yml
new file mode 100644
index 0000000000000000000000000000000000000000..56bce084546e73e2a166e2b0fe418b40f47e6920
--- /dev/null
+++ b/changelogs/unreleased/30272-bvl-reject-more-namespaces.yml
@@ -0,0 +1,4 @@
+---
+title: Improve validation of namespace & project paths
+merge_request: 10413
+author:
diff --git a/changelogs/unreleased/30535-display-whether-pages-is-enabled-in-the-admin-dashboard.yml b/changelogs/unreleased/30535-display-whether-pages-is-enabled-in-the-admin-dashboard.yml
new file mode 100644
index 0000000000000000000000000000000000000000..4452b13037b71dbf30b7d6ed9a8a9401a4d19d55
--- /dev/null
+++ b/changelogs/unreleased/30535-display-whether-pages-is-enabled-in-the-admin-dashboard.yml
@@ -0,0 +1,4 @@
+---
+title: Display GitLab Pages status in Admin Dashboard
+merge_request:
+author:
diff --git a/changelogs/unreleased/31057-unnecessary-padding-along-left-side-of-assignees-dropdown.yml b/changelogs/unreleased/31057-unnecessary-padding-along-left-side-of-assignees-dropdown.yml
new file mode 100644
index 0000000000000000000000000000000000000000..0d82bf878c706299d902264bcfbaab8bfe385694
--- /dev/null
+++ b/changelogs/unreleased/31057-unnecessary-padding-along-left-side-of-assignees-dropdown.yml
@@ -0,0 +1,4 @@
+---
+title: Show checkmark on current assignee in assignee dropdown
+merge_request: 10767
+author:
diff --git a/changelogs/unreleased/31254-change-git-commit-command-in-existing-folder.yml b/changelogs/unreleased/31254-change-git-commit-command-in-existing-folder.yml
new file mode 100644
index 0000000000000000000000000000000000000000..950336ea9327ab923627d11c7b3bc478da2577a0
--- /dev/null
+++ b/changelogs/unreleased/31254-change-git-commit-command-in-existing-folder.yml
@@ -0,0 +1,4 @@
+---
+title: Change Git commit command in Existing folder to git commit -m
+merge_request: 10900
+author: TM Lee
diff --git a/changelogs/unreleased/31560-workhose-gitaly-from-mirror.yml b/changelogs/unreleased/31560-workhose-gitaly-from-mirror.yml
new file mode 100644
index 0000000000000000000000000000000000000000..02c048cb3b4a2a5a313e2836c11f1b28776d5fe8
--- /dev/null
+++ b/changelogs/unreleased/31560-workhose-gitaly-from-mirror.yml
@@ -0,0 +1,4 @@
+---
+title: rickettm Add repo parameter to gitaly:install and workhorse:install rake tasks
+merge_request: 10979
+author: M. Ricketts
diff --git a/changelogs/unreleased/add-tanuki-ci-status-favicons.yml b/changelogs/unreleased/add-tanuki-ci-status-favicons.yml
new file mode 100644
index 0000000000000000000000000000000000000000..b60ad81947abf216d95f715ff5d160ea50dbffce
--- /dev/null
+++ b/changelogs/unreleased/add-tanuki-ci-status-favicons.yml
@@ -0,0 +1,4 @@
+---
+title: Updated CI status favicons to include the tanuki
+merge_request: 10923
+author:
diff --git a/changelogs/unreleased/dm-snippet-download-button.yml b/changelogs/unreleased/dm-snippet-download-button.yml
new file mode 100644
index 0000000000000000000000000000000000000000..09ece1e7f98440b42ae9dc80d2a08c094ce47e36
--- /dev/null
+++ b/changelogs/unreleased/dm-snippet-download-button.yml
@@ -0,0 +1,4 @@
+---
+title: Add download button to project snippets
+merge_request:
+author:
diff --git a/changelogs/unreleased/fix-import-export-missing-attributes.yml b/changelogs/unreleased/fix-import-export-missing-attributes.yml
new file mode 100644
index 0000000000000000000000000000000000000000..a1338b4eb48e843523bd13e3243a7d08d6df6898
--- /dev/null
+++ b/changelogs/unreleased/fix-import-export-missing-attributes.yml
@@ -0,0 +1,4 @@
+---
+title: Add missing project attributes to Import/Export
+merge_request:
+author:
diff --git a/changelogs/unreleased/fix-n-plus-one-project-features.yml b/changelogs/unreleased/fix-n-plus-one-project-features.yml
new file mode 100644
index 0000000000000000000000000000000000000000..1b19bd652249c34089beeedf6ab7ba19e31824e9
--- /dev/null
+++ b/changelogs/unreleased/fix-n-plus-one-project-features.yml
@@ -0,0 +1,4 @@
+---
+title: Remove N+1 queries in processing MR references
+merge_request:
+author:
diff --git a/config/initializers/active_record_query_trace.rb b/config/initializers/active_record_query_trace.rb
deleted file mode 100644
index 4b3c2803b3b36bc31df1b12500b8bc0bdad0288b..0000000000000000000000000000000000000000
--- a/config/initializers/active_record_query_trace.rb
+++ /dev/null
@@ -1,5 +0,0 @@
-if ENV['ENABLE_QUERY_TRACE']
-  require 'active_record_query_trace'
-
-  ActiveRecordQueryTrace.enabled = 'true'
-end
diff --git a/config/routes/admin.rb b/config/routes/admin.rb
index 52ba10604d40ede4804d961030efb243dc3cdb88..48993420ed9523c2507adad8f0feef89462ada6d 100644
--- a/config/routes/admin.rb
+++ b/config/routes/admin.rb
@@ -50,8 +50,10 @@ namespace :admin do
 
   resources :deploy_keys, only: [:index, :new, :create, :destroy]
 
-  resources :hooks, only: [:index, :create, :destroy] do
-    get :test
+  resources :hooks, only: [:index, :create, :edit, :update, :destroy] do
+    member do
+      get :test
+    end
   end
 
   resources :broadcast_messages, only: [:index, :edit, :create, :update, :destroy] do
diff --git a/config/routes/project.rb b/config/routes/project.rb
index 115ae2324b310e631c222a6fbdb02aee1e81d271..894faeb6188b400371b2181c724cc7284f1987ec 100644
--- a/config/routes/project.rb
+++ b/config/routes/project.rb
@@ -44,7 +44,7 @@ constraints(ProjectUrlConstrainer.new) do
 
       resources :snippets, concerns: :awardable, constraints: { id: /\d+/ } do
         member do
-          get 'raw'
+          get :raw
           post :mark_as_spam
         end
       end
@@ -138,6 +138,8 @@ constraints(ProjectUrlConstrainer.new) do
         collection do
           get :folder, path: 'folders/*id', constraints: { format: /(html|json)/ }
         end
+
+        resources :deployments, only: [:index]
       end
 
       resource :cycle_analytics, only: [:show]
@@ -185,7 +187,7 @@ constraints(ProjectUrlConstrainer.new) do
         end
       end
 
-      resources :hooks, only: [:index, :create, :destroy], constraints: { id: /\d+/ } do
+      resources :hooks, only: [:index, :create, :edit, :update, :destroy], constraints: { id: /\d+/ } do
         member do
           get :test
         end
diff --git a/config/routes/snippets.rb b/config/routes/snippets.rb
index 56534f677be51022e932511043fc7d186018cc00..dae83734fe67cd3ba5262810c6ed12ab2c0c8244 100644
--- a/config/routes/snippets.rb
+++ b/config/routes/snippets.rb
@@ -1,10 +1,17 @@
 resources :snippets, concerns: :awardable do
   member do
-    get 'raw'
-    get 'download'
+    get :raw
     post :mark_as_spam
     post :preview_markdown
   end
+
+  scope module: :snippets do
+    resources :notes, only: [:index, :create, :destroy, :update], concerns: :awardable, constraints: { id: /\d+/ } do
+      member do
+        delete :delete_attachment
+      end
+    end
+  end
 end
 
 get '/s/:username', to: redirect('/u/%{username}/snippets'),
diff --git a/db/migrate/20170327091750_add_created_at_index_to_deployments.rb b/db/migrate/20170327091750_add_created_at_index_to_deployments.rb
new file mode 100644
index 0000000000000000000000000000000000000000..fd6ed499b80a186db9f1f1de715020072aee5ad4
--- /dev/null
+++ b/db/migrate/20170327091750_add_created_at_index_to_deployments.rb
@@ -0,0 +1,15 @@
+class AddCreatedAtIndexToDeployments < ActiveRecord::Migration
+  include Gitlab::Database::MigrationHelpers
+
+  DOWNTIME = false
+
+  disable_ddl_transaction!
+
+  def up
+    add_concurrent_index :deployments, :created_at
+  end
+
+  def down
+    remove_concurrent_index :deployments, :created_at
+  end
+end
diff --git a/db/post_migrate/20170412174900_rename_reserved_dynamic_paths.rb b/db/post_migrate/20170412174900_rename_reserved_dynamic_paths.rb
new file mode 100644
index 0000000000000000000000000000000000000000..a23f83205f1488e6c8f51ed060b0b7924449eb9b
--- /dev/null
+++ b/db/post_migrate/20170412174900_rename_reserved_dynamic_paths.rb
@@ -0,0 +1,55 @@
+# See http://doc.gitlab.com/ce/development/migration_style_guide.html
+# for more information on how to write migrations for GitLab.
+
+class RenameReservedDynamicPaths < ActiveRecord::Migration
+  include Gitlab::Database::RenameReservedPathsMigration::V1
+
+  DOWNTIME = false
+
+  disable_ddl_transaction!
+
+  DISALLOWED_ROOT_PATHS = %w[
+    -
+    abuse_reports
+    api
+    autocomplete
+    explore
+    health_check
+    import
+    invites
+    jwt
+    koding
+    member
+    notification_settings
+    oauth
+    sent_notifications
+    unicorn_test
+    uploads
+    users
+  ]
+
+  DISALLOWED_WILDCARD_PATHS = %w[
+    environments/folders
+    gitlab-lfs/objects
+    info/lfs/objects
+  ]
+
+  DISSALLOWED_GROUP_PATHS = %w[
+    activity
+    avatar
+    group_members
+    labels
+    milestones
+    subgroups
+  ]
+
+  def up
+    rename_root_paths(DISALLOWED_ROOT_PATHS)
+    rename_wildcard_paths(DISALLOWED_WILDCARD_PATHS)
+    rename_child_paths(DISSALLOWED_GROUP_PATHS)
+  end
+
+  def down
+    # nothing to do
+  end
+end
diff --git a/db/schema.rb b/db/schema.rb
index b938657a186557b164103d34ecc6dc8407b4da68..be6684f3a6b4ef29b802b3fe3c70cedc679c309a 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -386,6 +386,7 @@ ActiveRecord::Schema.define(version: 20170426181740) do
     t.string "on_stop"
   end
 
+  add_index "deployments", ["created_at"], name: "index_deployments_on_created_at", using: :btree
   add_index "deployments", ["project_id", "environment_id", "iid"], name: "index_deployments_on_project_id_and_environment_id_and_iid", using: :btree
   add_index "deployments", ["project_id", "iid"], name: "index_deployments_on_project_id_and_iid", unique: true, using: :btree
 
diff --git a/doc/README.md b/doc/README.md
index 6406040da4b13bfab090f0b6f1cd2a678c7d3c54..4397465bd3d945ba2afb183eb88a48cc75f0badf 100644
--- a/doc/README.md
+++ b/doc/README.md
@@ -92,7 +92,7 @@ Take a step ahead and dive into GitLab's advanced features.
 
 - [GitLab Pages](user/project/pages/index.md): Build, test, and deploy your static website with GitLab Pages.
 - [Snippets](user/snippets.md): Snippets allow you to create little bits of code.
-- [Wikis](workflow/project_features.md#wiki): Enhance your repository documentation with built-in wikis.
+- [Wikis](user/project/wiki/index.md): Enhance your repository documentation with built-in wikis.
 
 ### Continuous Integration, Delivery, and Deployment
 
diff --git a/doc/administration/high_availability/load_balancer.md b/doc/administration/high_availability/load_balancer.md
index d9ca74ca1a3829bf6e4b1808c6f8268a75cd2912..359de0efadb6aaffb9b5b1cae1353495e1b781af 100644
--- a/doc/administration/high_availability/load_balancer.md
+++ b/doc/administration/high_availability/load_balancer.md
@@ -13,7 +13,7 @@ you need to use with GitLab.
 | LB Port | Backend Port | Protocol        |
 | ------- | ------------ | --------------- |
 | 80      | 80           | HTTP  [^1]      |
-| 443     | 443          | HTTPS [^1] [^2] |
+| 443     | 443          | TCP or HTTPS [^1] [^2] |
 | 22      | 22           | TCP             |
 
 ## GitLab Pages Ports
diff --git a/doc/administration/integration/terminal.md b/doc/administration/integration/terminal.md
index 3b5ee86b68b35576c04f1524baab504d445bf4c5..91e844c7b4200048a2b64408b92d2b1d0972eefd 100644
--- a/doc/administration/integration/terminal.md
+++ b/doc/administration/integration/terminal.md
@@ -32,7 +32,7 @@ In brief:
 
 As web terminals use WebSockets, every HTTP/HTTPS reverse proxy in front of
 Workhorse needs to be configured to pass the `Connection` and `Upgrade` headers
-through to the next one in the chain. If you installed Gitlab using Omnibus, or
+through to the next one in the chain. If you installed GitLab using Omnibus, or
 from source, starting with GitLab 8.15, this should be done by the default
 configuration, so there's no need for you to do anything.
 
@@ -58,7 +58,7 @@ document for more details.
 If you'd like to disable web terminal support in GitLab, just stop passing
 the `Connection` and `Upgrade` hop-by-hop headers in the *first* HTTP reverse
 proxy in the chain. For most users, this will be the NGINX server bundled with
-Omnibus Gitlab, in which case, you need to:
+Omnibus GitLab, in which case, you need to:
 
 * Find the `nginx['proxy_set_headers']` section of your `gitlab.rb` file
 * Ensure the whole block is uncommented, and then comment out or remove the
diff --git a/doc/ci/img/pipelines_grouped.png b/doc/ci/img/pipelines_grouped.png
new file mode 100644
index 0000000000000000000000000000000000000000..06f52e033207d64a510d7c5ac6a69d514254e61e
Binary files /dev/null and b/doc/ci/img/pipelines_grouped.png differ
diff --git a/doc/ci/img/pipelines_index.png b/doc/ci/img/pipelines_index.png
new file mode 100644
index 0000000000000000000000000000000000000000..3b522a9c5e40255cb3612b5003d40e3274cc0a7f
Binary files /dev/null and b/doc/ci/img/pipelines_index.png differ
diff --git a/doc/ci/img/pipelines_mini_graph.png b/doc/ci/img/pipelines_mini_graph.png
new file mode 100644
index 0000000000000000000000000000000000000000..042c8ffeef52cb30dbd96e868ff33e049720bf3c
Binary files /dev/null and b/doc/ci/img/pipelines_mini_graph.png differ
diff --git a/doc/ci/img/pipelines_mini_graph_simple.png b/doc/ci/img/pipelines_mini_graph_simple.png
new file mode 100644
index 0000000000000000000000000000000000000000..eb36c09b2d48d0256ec3eb33df682e00b37886e8
Binary files /dev/null and b/doc/ci/img/pipelines_mini_graph_simple.png differ
diff --git a/doc/ci/img/pipelines_mini_graph_sorting.png b/doc/ci/img/pipelines_mini_graph_sorting.png
new file mode 100644
index 0000000000000000000000000000000000000000..3a4e545336009eb0a7c09bf2d581e851f3e7678d
Binary files /dev/null and b/doc/ci/img/pipelines_mini_graph_sorting.png differ
diff --git a/doc/ci/pipelines.md b/doc/ci/pipelines.md
index db92a4b0d8051e7ee937f408c88178df728e1c37..5a2b61fb0cb889252a9ee72684de7fbb5a13c597 100644
--- a/doc/ci/pipelines.md
+++ b/doc/ci/pipelines.md
@@ -1,7 +1,6 @@
 # Introduction to pipelines and jobs
 
->**Note:**
-Introduced in GitLab 8.8.
+> Introduced in GitLab 8.8.
 
 ## Pipelines
 
@@ -9,11 +8,17 @@ A pipeline is a group of [jobs][] that get executed in [stages][](batches).
 All of the jobs in a stage are executed in parallel (if there are enough
 concurrent [Runners]), and if they all succeed, the pipeline moves on to the
 next stage. If one of the jobs fails, the next stage is not (usually)
-executed.
+executed. You can access the pipelines page in your project's **Pipelines** tab.
+
+In the following image you can see that the pipeline consists of four stages
+(`build`, `test`, `staging`, `production`) each one having one or more jobs.
+
+>**Note:**
+GitLab capitalizes the stages' names when shown in the [pipeline graphs](#pipeline-graphs).
 
 ![Pipelines example](img/pipelines.png)
 
-## Types of Pipelines
+## Types of pipelines
 
 There are three types of pipelines that often use the single shorthand of "pipeline". People often talk about them as if each one is "the" pipeline, but really, they're just pieces of a single, comprehensive pipeline.
 
@@ -23,7 +28,7 @@ There are three types of pipelines that often use the single shorthand of "pipel
 2. **Deploy Pipeline**: Deploy stage(s) defined in `.gitlab-ci.yml` The flow of deploying code to servers through various stages: e.g. development to staging to production
 3. **Project Pipeline**: Cross-project CI dependencies [triggered via API][triggers], particularly for micro-services, but also for complicated build dependencies: e.g. api -> front-end, ce/ee -> omnibus.
 
-## Development Workflows
+## Development workflows
 
 Pipelines accommodate several development workflows:
 
@@ -45,18 +50,141 @@ confused with a `build` job or `build` stage.
 Pipelines are defined in `.gitlab-ci.yml` by specifying [jobs] that run in
 [stages].
 
-See full [documentation](yaml/README.md#jobs).
+See the reference [documentation for jobs](yaml/README.md#jobs).
 
 ## Seeing pipeline status
 
-You can find the current and historical pipeline runs under **Pipelines** for
-your project.
+You can find the current and historical pipeline runs under your project's
+**Pipelines** tab. Clicking on a pipeline will show the jobs that were run for
+that pipeline.
+
+![Pipelines index page](img/pipelines_index.png)
 
 ## Seeing job status
 
-Clicking on a pipeline will show the jobs that were run for that pipeline.
+When you visit a single pipeline you can see the related jobs for that pipeline.
 Clicking on an individual job will show you its job trace, and allow you to
-cancel the job, retry it,  or erase the job trace.
+cancel the job, retry it, or erase the job trace.
+
+![Pipelines example](img/pipelines.png)
+
+## Pipeline graphs
+
+> [Introduced][ce-5742] in GitLab 8.11.
+
+Pipelines can be complex structures with many sequential and parallel jobs.
+To make it a little easier to see what is going on, you can view a graph
+of a single pipeline and its status.
+
+A pipeline graph can be shown in two different ways depending on what page you
+are on.
+
+---
+
+The regular pipeline graph that shows the names of the jobs of each stage can
+be found when you are on a [single pipeline page](#seeing-pipeline-status).
+
+![Pipelines example](img/pipelines.png)
+
+Then, there is the pipeline mini graph which takes less space and can give you a
+quick glance if all jobs pass or something failed. The pipeline mini graph can
+be found when you visit:
+
+- the pipelines index page
+- a single commit page
+- a merge request page
+
+That way, you can see all related jobs for a single commit and the net result
+of each stage of your pipeline. This allows you to quickly see what failed and
+fix it. Stages in pipeline mini graphs are collapsible. Hover your mouse over
+them and click to expand their jobs.
+
+| **Mini graph** | **Mini graph expanded** |
+| :------------: | :---------------------: |
+| ![Pipelines mini graph](img/pipelines_mini_graph_simple.png) | ![Pipelines mini graph extended](img/pipelines_mini_graph.png) |
+
+### Grouping similar jobs in the pipeline graph
+
+> [Introduced][ce-6242] in GitLab 8.12.
+
+If you have many similar jobs, your pipeline graph becomes very long and hard
+to read. For that reason, similar jobs can automatically be grouped together.
+If the job names are formatted in certain ways, they will be collapsed into
+a single group in regular pipeline graphs (not the mini graphs).
+You'll know when a pipeline has grouped jobs if you don't see the retry or
+cancel button inside them. Hovering over them will show the number of grouped
+jobs. Click to expand them.
+
+![Grouped pipelines](img/pipelines_grouped.png)
+
+The basic requirements is that there are two numbers separated with one of
+the following (you can even use them interchangeably):
+
+- a space
+- a backslash (`/`)
+- a colon (`:`)
+
+>**Note:**
+More specifically, [it uses][regexp] this regular expression: `\d+[\s:\/\\]+\d+\s*`.
+
+The jobs will be ordered by comparing those two numbers from left to right. You
+usually want the first to be the index and the second the total.
+
+For example, the following jobs will be grouped under a job named `test`:
+
+- `test 0 3` => `test`
+- `test 1 3` => `test`
+- `test 2 3` => `test`
+
+The following jobs will be grouped under a job named `test ruby`:
+
+- `test 1:2 ruby` => `test ruby`
+- `test 2:2 ruby` => `test ruby`
+
+The following jobs will be grouped under a job named `test ruby` as well:
+
+- `1/3 test ruby` => `test ruby`
+- `2/3 test ruby` => `test ruby`
+- `3/3 test ruby` => `test ruby`
+
+### Manual actions from the pipeline graph
+
+> [Introduced][ce-7931] in GitLab 8.15.
+
+[Manual actions][manual] allow you to require manual interaction before moving
+forward with a particular job in CI. Your entire pipeline can run automatically,
+but the actual [deploy to production][env-manual] will require a click.
+
+You can do this straight from the pipeline graph. Just click on the play button
+to execute that particular job. For example, in the image below, the `production`
+stage has a job with a manual action.
+
+![Pipelines example](img/pipelines.png)
+
+### Ordering of jobs in pipeline graphs
+
+**Regular pipeline graph**
+
+In the single pipeline page, jobs are sorted by name.
+
+**Mini pipeline graph**
+
+> [Introduced][ce-9760] in GitLab 9.0.
+
+In the pipeline mini graphs, the jobs are sorted first by severity and then
+by name. The order of severity is:
+
+- failed
+- warning
+- pending
+- running
+- manual
+- canceled
+- success
+- skipped
+- created
+
+![Pipeline mini graph sorting](img/pipelines_mini_graph_sorting.png)
 
 ## How the pipeline duration is calculated
 
@@ -96,7 +224,14 @@ respective link in the [Pipelines settings] page.
 
 [jobs]: #jobs
 [jobs-yaml]: yaml/README.md#jobs
+[manual]: yaml/README.md#manual
+[env-manual]: environments.md#manually-deploying-to-environments
 [stages]: yaml/README.md#stages
 [runners]: runners/README.html
 [pipelines settings]: ../user/project/pipelines/settings.md
 [triggers]: triggers/README.md
+[ce-5742]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/5742
+[ce-6242]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/6242
+[ce-7931]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/7931
+[ce-9760]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/9760
+[regexp]: https://gitlab.com/gitlab-org/gitlab-ce/blob/2f3dc314f42dbd79813e6251792853bc231e69dd/app/models/commit_status.rb#L99
diff --git a/doc/development/migration_style_guide.md b/doc/development/migration_style_guide.md
index 3e8b709c18f7be75320a51c376b193f170ee59b1..77ba2a5fd87802fa434018f373fce2978fa68b47 100644
--- a/doc/development/migration_style_guide.md
+++ b/doc/development/migration_style_guide.md
@@ -270,3 +270,28 @@ end
 
 When doing so be sure to explicitly set the model's table name so it's not
 derived from the class name or namespace.
+
+### Renaming reserved paths
+
+When a new route for projects is introduced that could conflict with any
+existing records. The path for this records should be renamed, and the
+related data should be moved on disk.
+
+Since we had to do this a few times already, there are now some helpers to help
+with this.
+
+To use this you can include `Gitlab::Database::RenameReservedPathsMigration::V1`
+in your migration. This will provide 3 methods which you can pass one or more
+paths that need to be rejected.
+
+**`rename_root_paths`**: This will rename the path of all _namespaces_ with the
+given name that don't have a `parent_id`.
+
+**`rename_child_paths`**: This will rename the path of all _namespaces_ with the
+given name that have a `parent_id`.
+
+**`rename_wildcard_paths`**: This will rename the path of all _projects_, and all
+_namespaces_ that have a `project_id`.
+
+The `path` column for these rows will be renamed to their previous value followed
+by an integer. For example: `users` would turn into `users0`
diff --git a/doc/development/testing.md b/doc/development/testing.md
index 9b0b9808827ce1b84ddf2706c620916dfbd7dc59..6d8b846d27fe2d347bc8f7914182f08332fadf54 100644
--- a/doc/development/testing.md
+++ b/doc/development/testing.md
@@ -188,7 +188,8 @@ Please consult the [dedicated "Frontend testing" guide](./fe_guide/testing.md).
 ### General Guidelines
 
 - Use a single, top-level `describe ClassName` block.
-- Use `described_class` instead of repeating the class name being described.
+- Use `described_class` instead of repeating the class name being described
+  (_this is enforced by RuboCop_).
 - Use `.method` to describe class methods and `#method` to describe instance
   methods.
 - Use `context` to test branching logic.
@@ -197,7 +198,7 @@ Please consult the [dedicated "Frontend testing" guide](./fe_guide/testing.md).
 - Don't `describe` symbols (see [Gotchas](gotchas.md#dont-describe-symbols)).
 - Don't assert against the absolute value of a sequence-generated attribute (see [Gotchas](gotchas.md#dont-assert-against-the-absolute-value-of-a-sequence-generated-attribute)).
 - Don't supply the `:each` argument to hooks since it's the default.
-- Prefer `not_to` to `to_not` (_this is enforced by Rubocop_).
+- Prefer `not_to` to `to_not` (_this is enforced by RuboCop_).
 - Try to match the ordering of tests to the ordering within the class.
 - Try to follow the [Four-Phase Test][four-phase-test] pattern, using newlines
   to separate phases.
diff --git a/doc/gitlab-basics/create-project.md b/doc/gitlab-basics/create-project.md
index 1c549844ee1cf7e2b2f9492b652d25591fb9aa2f..2513f4b420a675618d204a10b26d068dc9099599 100644
--- a/doc/gitlab-basics/create-project.md
+++ b/doc/gitlab-basics/create-project.md
@@ -1,24 +1,28 @@
 # How to create a project in GitLab
 
-There are two ways to create a new project in GitLab.
-
-1. While in your dashboard, you can create a new project using the **New project**
-   green button or you can use the cross icon in the upper right corner next to
-   your avatar which is always visible.
+1. In your dashboard, click the green **New project** button or use the plus
+   icon in the upper right corner of the navigation bar.
 
     ![Create a project](img/create_new_project_button.png)
 
-1. From there you can see several options.
+1. This opens the **New project** page.
 
     ![Project information](img/create_new_project_info.png)
 
-1. Fill out the information:
-
-  1. "Project name" is the name of your project (you can't use special characters,
-     but you can use spaces, hyphens, underscores or even emojis).
-  1. The "Project description" is optional and will be shown in your project's
-     dashboard so others can briefly understand what your project is about.
-  1. Select a [visibility level](../public_access/public_access.md).
-  1. You can also [import your existing projects](../workflow/importing/README.md).
-
-1. Finally, click **Create project**.
+1. Provide the following information:
+    - Enter the name of your project in the **Project name** field. You can't use
+      special characters, but you can use spaces, hyphens, underscores or even
+      emoji.
+    - If you have a project in a different repository, you can [import it] by
+      clicking an **Import project from** button provided this is enabled in
+      your GitLab instance. Ask your administrator if not.
+    - The **Project description (optional)** field enables you to enter a
+      description for your project's dashboard, which will help others
+      understand what your project is about. Though it's not required, it's a good
+      idea to fill this in.
+    - Changing the **Visibility Level** modifies the project's
+      [viewing and access rights](../public_access/public_access.md) for users.
+
+1. Click **Create project**.
+
+[import it]: ../workflow/importing/README.md
diff --git a/doc/gitlab-basics/img/create_new_project_button.png b/doc/gitlab-basics/img/create_new_project_button.png
index 8d7a69e55ed9a458bc65db3b8d8e819becab0e19..567f104880f900a5343fffa4b62c1bb5021e1f50 100644
Binary files a/doc/gitlab-basics/img/create_new_project_button.png and b/doc/gitlab-basics/img/create_new_project_button.png differ
diff --git a/doc/install/installation.md b/doc/install/installation.md
index b6bbc2a0af6c80b1abfad7839f93824d99ba06e6..dc807d93bbb9ded71a1a2209bb5d0ca3ce0af6dd 100644
--- a/doc/install/installation.md
+++ b/doc/install/installation.md
@@ -423,6 +423,11 @@ which is the recommended location.
 
     sudo -u git -H bundle exec rake "gitlab:workhorse:install[/home/git/gitlab-workhorse]" RAILS_ENV=production
 
+You can specify a different Git repository by providing it as an extra paramter:
+
+    sudo -u git -H bundle exec rake "gitlab:workhorse:install[/home/git/gitlab-workhorse,https://example.com/gitlab-workhorse.git]" RAILS_ENV=production
+
+
 ### Initialize Database and Activate Advanced Features
 
     sudo -u git -H bundle exec rake gitlab:setup RAILS_ENV=production
@@ -466,6 +471,12 @@ with setting up Gitaly until you upgrade to GitLab 9.2 or later.
     # Fetch Gitaly source with Git and compile with Go
     sudo -u git -H bundle exec rake "gitlab:gitaly:install[/home/git/gitaly]" RAILS_ENV=production
 
+You can specify a different Git repository by providing it as an extra paramter:
+
+    sudo -u git -H bundle exec rake "gitlab:gitaly:install[/home/git/gitaly,https://example.com/gitaly.git]" RAILS_ENV=production
+
+Next, make sure gitaly configured:
+
     # Restrict Gitaly socket access
     sudo chmod 0700 /home/git/gitlab/tmp/sockets/private
     sudo chown git /home/git/gitlab/tmp/sockets/private
diff --git a/doc/update/8.10-to-8.11.md b/doc/update/8.10-to-8.11.md
index e5e3cd395dff795856d1f130fdb19c7d9fb4ac51..e538983e603c7c2c3e1735dc6ccd2cb8feb24de7 100644
--- a/doc/update/8.10-to-8.11.md
+++ b/doc/update/8.10-to-8.11.md
@@ -49,6 +49,8 @@ sudo gem install bundler --no-ri --no-rdoc
 ### 4. Get latest code
 
 ```bash
+cd /home/git/gitlab
+
 sudo -u git -H git fetch --all
 sudo -u git -H git checkout -- db/schema.rb # local changes will be restored automatically
 ```
diff --git a/doc/update/8.11-to-8.12.md b/doc/update/8.11-to-8.12.md
index d6b3b0ffa5a4dc9ecf308b046082ad988aa17668..604166beb56325b3c5db18ffe3bf1884b22a0208 100644
--- a/doc/update/8.11-to-8.12.md
+++ b/doc/update/8.11-to-8.12.md
@@ -49,6 +49,8 @@ sudo gem install bundler --no-ri --no-rdoc
 ### 4. Get latest code
 
 ```bash
+cd /home/git/gitlab
+
 sudo -u git -H git fetch --all
 sudo -u git -H git checkout -- db/schema.rb # local changes will be restored automatically
 ```
diff --git a/doc/update/8.12-to-8.13.md b/doc/update/8.12-to-8.13.md
index ed0e668d854f8f8c5489a83958cc9bf96d3a4832..d83965131f5477dff25f11302f12d169c9b121e0 100644
--- a/doc/update/8.12-to-8.13.md
+++ b/doc/update/8.12-to-8.13.md
@@ -49,6 +49,8 @@ sudo gem install bundler --no-ri --no-rdoc
 ### 4. Get latest code
 
 ```bash
+cd /home/git/gitlab
+
 sudo -u git -H git fetch --all
 sudo -u git -H git checkout -- db/schema.rb # local changes will be restored automatically
 ```
diff --git a/doc/update/8.13-to-8.14.md b/doc/update/8.13-to-8.14.md
index aa1c659717e1a21c297e9b77a6cdbc2c1738db2b..aaadcec8ac0fc7a64c43d922fe34f469440baeb7 100644
--- a/doc/update/8.13-to-8.14.md
+++ b/doc/update/8.13-to-8.14.md
@@ -49,6 +49,8 @@ sudo gem install bundler --no-ri --no-rdoc
 ### 4. Get latest code
 
 ```bash
+cd /home/git/gitlab
+
 sudo -u git -H git fetch --all
 sudo -u git -H git checkout -- db/schema.rb # local changes will be restored automatically
 ```
diff --git a/doc/user/project/wiki/img/wiki_create_home_page.png b/doc/user/project/wiki/img/wiki_create_home_page.png
new file mode 100644
index 0000000000000000000000000000000000000000..f50f564034ca3d6d20431a4d79af85d2b1a6517a
Binary files /dev/null and b/doc/user/project/wiki/img/wiki_create_home_page.png differ
diff --git a/doc/user/project/wiki/img/wiki_create_new_page.png b/doc/user/project/wiki/img/wiki_create_new_page.png
new file mode 100644
index 0000000000000000000000000000000000000000..c19124a892304d5e773909f0dab4a4ba8eaee152
Binary files /dev/null and b/doc/user/project/wiki/img/wiki_create_new_page.png differ
diff --git a/doc/user/project/wiki/img/wiki_create_new_page_modal.png b/doc/user/project/wiki/img/wiki_create_new_page_modal.png
new file mode 100644
index 0000000000000000000000000000000000000000..ece437967dcba0744c7828ca2a9f6a1de07765c6
Binary files /dev/null and b/doc/user/project/wiki/img/wiki_create_new_page_modal.png differ
diff --git a/doc/user/project/wiki/img/wiki_page_history.png b/doc/user/project/wiki/img/wiki_page_history.png
new file mode 100644
index 0000000000000000000000000000000000000000..0e6af1b468d77805e96c7c4f9e15e8fc6d8c44d1
Binary files /dev/null and b/doc/user/project/wiki/img/wiki_page_history.png differ
diff --git a/doc/user/project/wiki/img/wiki_sidebar.png b/doc/user/project/wiki/img/wiki_sidebar.png
new file mode 100644
index 0000000000000000000000000000000000000000..59814e2a06e4010dfc35a9814cc4bb9721c6110c
Binary files /dev/null and b/doc/user/project/wiki/img/wiki_sidebar.png differ
diff --git a/doc/user/project/wiki/index.md b/doc/user/project/wiki/index.md
new file mode 100644
index 0000000000000000000000000000000000000000..e9ee1abc6c102eb8cfbd256bd32a320f1d36f641
--- /dev/null
+++ b/doc/user/project/wiki/index.md
@@ -0,0 +1,97 @@
+# Wiki
+
+A separate system for documentation called Wiki, is built right into each
+GitLab project. It is enabled by default on all new projects and you can find
+it under **Wiki** in your project.
+
+Wikis are very convenient if you don't want to keep you documentation in your
+repository, but you do want to keep it in the same project where your code
+resides.
+
+You can create Wiki pages in the web interface or
+[locally using Git](#adding-and-editing-wiki-pages-locally) since every Wiki is
+a separate Git repository.
+
+>**Note:**
+A [permission level][permissions] of **Guest** is needed to view a Wiki and
+**Developer** is needed to create and edit Wiki pages.
+
+## First time creating the Home page
+
+The first time you visit a Wiki, you will be directed to create the Home page.
+The Home page is necessary to be created since it serves as the landing page
+when viewing a Wiki. You only have to fill in the **Content** section and click
+**Create page**. You can always edit it later, so go ahead and write a welcome
+message.
+
+![New home page](img/wiki_create_home_page.png)
+
+## Creating a new wiki page
+
+Create a new page by clicking the **New page** button that can be found
+in all wiki pages. You will be asked to fill in the page name from which GitLab
+will create the path to the page. You can specify a full path for the new file
+and any missing directories will be created automatically.
+
+![New page modal](img/wiki_create_new_page_modal.png)
+
+Once you enter the page name, it's time to fill in its content. GitLab wikis
+support Markdown, RDoc and AsciiDoc. For Markdown based pages, all the
+[Markdown features](../../markdown.md) are supported and for links there is
+some [wiki specific](../../markdown.md#wiki-specific-markdown) behavior.
+
+>**Note:**
+The wiki is based on a Git repository and contains only text files. Uploading
+files via the web interface will upload them in GitLab itself, and they will
+not be available if you clone the wiki repo locally.
+
+In the web interface the commit message is optional, but the GitLab Wiki is
+based on Git and needs a commit message, so one will be created for you if you
+do not enter one.
+
+When you're ready, click the **Create page** and the new page will be created.
+
+![New page](img/wiki_create_new_page.png)
+
+## Editing a wiki page
+
+To edit a page, simply click on the **Edit** button. From there on, you can
+change its content. When done, click **Save changes** for the changes to take
+effect.
+
+## Deleting a wiki page
+
+You can find the **Delete** button only when editing a page. Click on it and
+confirm you want the page to be deleted.
+
+## Viewing a list of all created wiki pages
+
+Every wiki has a sidebar from which a short list of the created pages can be
+found. The list is ordered alphabetically.
+
+![Wiki sidebar](img/wiki_sidebar.png)
+
+If you have many pages, not all will be listed in the sidebar. Click on
+**More pages** to see all of them.
+
+## Viewing the history of a wiki page
+
+The changes of a wiki page over time are recorded in the wiki's Git repository,
+and you can view them by clicking the **Page history** button.
+
+From the history page you can see the revision of the page (Git commit SHA), its
+author, the commit message, when it was last updated and the page markup format.
+To see how a previous version of the page looked like, click on a revision
+number.
+
+![Wiki page history](img/wiki_page_history.png)
+
+## Adding and editing wiki pages locally
+
+Since wikis are based on Git repositories, you can clone them locally and edit
+them like you would do with every other Git repository.
+
+On the right sidebar, click on **Clone repository** and follow the on-screen
+instructions.
+
+[permissions]: ../../permissions.md
diff --git a/doc/workflow/project_features.md b/doc/workflow/project_features.md
index f19e7df8c9aa59fcf4b4c8a04c124ea4ac7db2a9..3f5de2bd4b1dfd06b43848056e68fd7ca07f9a88 100644
--- a/doc/workflow/project_features.md
+++ b/doc/workflow/project_features.md
@@ -26,6 +26,8 @@ This is a separate system for documentation, built right into GitLab.
 
 It is source controlled and is very convenient if you don't want to keep you documentation in your source code, but you do want to keep it in your GitLab project.
 
+[Read more about Wikis.](../user/project/wiki/index.md)
+
 ## Snippets
 
 Snippets are little bits of code or text.
diff --git a/features/support/env.rb b/features/support/env.rb
index 06c804b1db7fbc0922156f5d7e1aba037d7471b9..92d13bea4b69a8feaae06386204b2cf97b1218c3 100644
--- a/features/support/env.rb
+++ b/features/support/env.rb
@@ -10,7 +10,7 @@ if ENV['CI']
   Knapsack::Adapters::SpinachAdapter.bind
 end
 
-%w(select2_helper test_env repo_helpers wait_for_ajax sidekiq).each do |f|
+%w(select2_helper test_env repo_helpers wait_for_ajax wait_for_requests sidekiq).each do |f|
   require Rails.root.join('spec', 'support', f)
 end
 
@@ -30,6 +30,13 @@ Spinach.hooks.before_run do
   include FactoryGirl::Syntax::Methods
 end
 
+Spinach.hooks.after_feature do |feature_data|
+  if feature_data.scenarios.flat_map(&:tags).include?('javascript')
+    include WaitForRequests
+    wait_for_requests_complete
+  end
+end
+
 module StdoutReporterWithScenarioLocation
   # Override the standard reporter to show filename and line number next to each
   # scenario for easy, focused re-runs
diff --git a/lib/api/merge_requests.rb b/lib/api/merge_requests.rb
index e5793fbc5cb1303af50298ad701fef327938ab3f..710deba5ae3f230ae0e115d380bca69db23d1a72 100644
--- a/lib/api/merge_requests.rb
+++ b/lib/api/merge_requests.rb
@@ -20,6 +20,8 @@ module API
             error!(errors[:validate_fork], 422)
           elsif errors[:validate_branches].any?
             conflict!(errors[:validate_branches])
+          elsif errors[:base].any?
+            error!(errors[:base], 422)
           end
 
           render_api_error!(errors, 400)
diff --git a/lib/api/v3/merge_requests.rb b/lib/api/v3/merge_requests.rb
index 3077240e6506eb33426094b91b29792a27616b69..1616142a619db6955c02f74619025b50820303b9 100644
--- a/lib/api/v3/merge_requests.rb
+++ b/lib/api/v3/merge_requests.rb
@@ -23,6 +23,8 @@ module API
               error!(errors[:validate_fork], 422)
             elsif errors[:validate_branches].any?
               conflict!(errors[:validate_branches])
+            elsif errors[:base].any?
+              error!(errors[:base], 422)
             end
 
             render_api_error!(errors, 400)
diff --git a/lib/banzai/reference_parser/merge_request_parser.rb b/lib/banzai/reference_parser/merge_request_parser.rb
index 84a28b33d7c224a4810bc46459d2b03b3c3019c8..8b0662749fdef21e378e9d58eaaed1f058a2281c 100644
--- a/lib/banzai/reference_parser/merge_request_parser.rb
+++ b/lib/banzai/reference_parser/merge_request_parser.rb
@@ -33,7 +33,8 @@ module Banzai
                 { namespace: :owner },
                 { group: [:owners, :group_members] },
                 :invited_groups,
-                :project_members
+                :project_members,
+                :project_feature
               ]
             }),
           self.class.data_attribute
diff --git a/lib/constraints/group_url_constrainer.rb b/lib/constraints/group_url_constrainer.rb
index bae4db1ca4d4231e6e1b0d6b848a830b6bc4ad1c..1501f64d537af753a14cbfbc0db3f59a72cde716 100644
--- a/lib/constraints/group_url_constrainer.rb
+++ b/lib/constraints/group_url_constrainer.rb
@@ -2,16 +2,8 @@ class GroupUrlConstrainer
   def matches?(request)
     id = request.params[:id]
 
-    return false unless valid?(id)
+    return false unless DynamicPathValidator.valid?(id)
 
     Group.find_by_full_path(id).present?
   end
-
-  private
-
-  def valid?(id)
-    id.split('/').all? do |namespace|
-      NamespaceValidator.valid?(namespace)
-    end
-  end
 end
diff --git a/lib/constraints/project_url_constrainer.rb b/lib/constraints/project_url_constrainer.rb
index a10b4657d7d32c130cb2401d97eb476a7f2f240d..d0ce2caffffc44d5808258c1b71786dddc0d158c 100644
--- a/lib/constraints/project_url_constrainer.rb
+++ b/lib/constraints/project_url_constrainer.rb
@@ -4,9 +4,7 @@ class ProjectUrlConstrainer
     project_path = request.params[:project_id] || request.params[:id]
     full_path = namespace_path + '/' + project_path
 
-    unless ProjectPathValidator.valid?(project_path)
-      return false
-    end
+    return false unless DynamicPathValidator.valid?(full_path)
 
     Project.find_by_full_path(full_path).present?
   end
diff --git a/lib/gitlab/database/migration_helpers.rb b/lib/gitlab/database/migration_helpers.rb
index 6dabbe0264c2b23f9e44a8e3a2b4fad423126fa3..298b1a1f4e6eafbdd6fa45d7b0ba6f5cb0345f10 100644
--- a/lib/gitlab/database/migration_helpers.rb
+++ b/lib/gitlab/database/migration_helpers.rb
@@ -498,6 +498,29 @@ module Gitlab
 
         columns(table).find { |column| column.name == name }
       end
+
+      # This will replace the first occurance of a string in a column with
+      # the replacement
+      # On postgresql we can use `regexp_replace` for that.
+      # On mysql we find the location of the pattern, and overwrite it
+      # with the replacement
+      def replace_sql(column, pattern, replacement)
+        quoted_pattern = Arel::Nodes::Quoted.new(pattern.to_s)
+        quoted_replacement = Arel::Nodes::Quoted.new(replacement.to_s)
+
+        if Database.mysql?
+          locate = Arel::Nodes::NamedFunction.
+            new('locate', [quoted_pattern, column])
+          insert_in_place = Arel::Nodes::NamedFunction.
+            new('insert', [column, locate, pattern.size, quoted_replacement])
+
+          Arel::Nodes::SqlLiteral.new(insert_in_place.to_sql)
+        else
+          replace = Arel::Nodes::NamedFunction.
+            new("regexp_replace", [column, quoted_pattern, quoted_replacement])
+          Arel::Nodes::SqlLiteral.new(replace.to_sql)
+        end
+      end
     end
   end
 end
diff --git a/lib/gitlab/database/rename_reserved_paths_migration/v1.rb b/lib/gitlab/database/rename_reserved_paths_migration/v1.rb
new file mode 100644
index 0000000000000000000000000000000000000000..89530082cd2d6aecdada8bd31414dc7fa38ee29a
--- /dev/null
+++ b/lib/gitlab/database/rename_reserved_paths_migration/v1.rb
@@ -0,0 +1,35 @@
+# This module can be included in migrations to make it easier to rename paths
+# of `Namespace` & `Project` models certain paths would become `reserved`.
+#
+# If the way things are stored on the filesystem related to namespaces and
+# projects ever changes. Don't update this module, or anything nested in `V1`,
+# since it needs to keep functioning for all migrations using it using the state
+# that the data is in at the time. Instead, create a `V2` module that implements
+# the new way of reserving paths.
+module Gitlab
+  module Database
+    module RenameReservedPathsMigration
+      module V1
+        def self.included(kls)
+          kls.include(MigrationHelpers)
+        end
+
+        def rename_wildcard_paths(one_or_more_paths)
+          rename_child_paths(one_or_more_paths)
+          paths = Array(one_or_more_paths)
+          RenameProjects.new(paths, self).rename_projects
+        end
+
+        def rename_child_paths(one_or_more_paths)
+          paths = Array(one_or_more_paths)
+          RenameNamespaces.new(paths, self).rename_namespaces(type: :child)
+        end
+
+        def rename_root_paths(paths)
+          paths = Array(paths)
+          RenameNamespaces.new(paths, self).rename_namespaces(type: :top_level)
+        end
+      end
+    end
+  end
+end
diff --git a/lib/gitlab/database/rename_reserved_paths_migration/v1/migration_classes.rb b/lib/gitlab/database/rename_reserved_paths_migration/v1/migration_classes.rb
new file mode 100644
index 0000000000000000000000000000000000000000..4fdcb682c2f694fee4ca1cd58265a284434e3c86
--- /dev/null
+++ b/lib/gitlab/database/rename_reserved_paths_migration/v1/migration_classes.rb
@@ -0,0 +1,76 @@
+module Gitlab
+  module Database
+    module RenameReservedPathsMigration
+      module V1
+        module MigrationClasses
+          module Routable
+            def full_path
+              if route && route.path.present?
+                @full_path ||= route.path
+              else
+                update_route if persisted?
+
+                build_full_path
+              end
+            end
+
+            def build_full_path
+              if parent && path
+                parent.full_path + '/' + path
+              else
+                path
+              end
+            end
+
+            def update_route
+              prepare_route
+              route.save
+            end
+
+            def prepare_route
+              route || build_route(source: self)
+              route.path = build_full_path
+              @full_path = nil
+            end
+          end
+
+          class Namespace < ActiveRecord::Base
+            include MigrationClasses::Routable
+            self.table_name = 'namespaces'
+            belongs_to :parent,
+                       class_name: "#{MigrationClasses.name}::Namespace"
+            has_one :route, as: :source
+            has_many :children,
+                     class_name: "#{MigrationClasses.name}::Namespace",
+                     foreign_key: :parent_id
+
+            # Overridden to have the correct `source_type` for the `route` relation
+            def self.name
+              'Namespace'
+            end
+          end
+
+          class Route < ActiveRecord::Base
+            self.table_name = 'routes'
+            belongs_to :source, polymorphic: true
+          end
+
+          class Project < ActiveRecord::Base
+            include MigrationClasses::Routable
+            has_one :route, as: :source
+            self.table_name = 'projects'
+
+            def repository_storage_path
+              Gitlab.config.repositories.storages[repository_storage]['path']
+            end
+
+            # Overridden to have the correct `source_type` for the `route` relation
+            def self.name
+              'Project'
+            end
+          end
+        end
+      end
+    end
+  end
+end
diff --git a/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_base.rb b/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_base.rb
new file mode 100644
index 0000000000000000000000000000000000000000..de4e6e7c4041ce1f1f5ce7514df5f57c3c4b3e20
--- /dev/null
+++ b/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_base.rb
@@ -0,0 +1,131 @@
+module Gitlab
+  module Database
+    module RenameReservedPathsMigration
+      module V1
+        class RenameBase
+          attr_reader :paths, :migration
+
+          delegate :update_column_in_batches,
+                   :replace_sql,
+                   to: :migration
+
+          def initialize(paths, migration)
+            @paths = paths
+            @migration = migration
+          end
+
+          def path_patterns
+            @path_patterns ||= paths.map { |path| "%#{path}" }
+          end
+
+          def rename_path_for_routable(routable)
+            old_path = routable.path
+            old_full_path = routable.full_path
+            # Only remove the last occurrence of the path name to get the parent namespace path
+            namespace_path = remove_last_occurrence(old_full_path, old_path)
+            new_path = rename_path(namespace_path, old_path)
+            new_full_path = join_routable_path(namespace_path, new_path)
+
+            # skips callbacks & validations
+            routable.class.where(id: routable).
+              update_all(path: new_path)
+
+            rename_routes(old_full_path, new_full_path)
+
+            [old_full_path, new_full_path]
+          end
+
+          def rename_routes(old_full_path, new_full_path)
+            replace_statement = replace_sql(Route.arel_table[:path],
+                                            old_full_path,
+                                            new_full_path)
+
+            update_column_in_batches(:routes, :path, replace_statement)  do |table, query|
+              query.where(MigrationClasses::Route.arel_table[:path].matches("#{old_full_path}%"))
+            end
+          end
+
+          def rename_path(namespace_path, path_was)
+            counter = 0
+            path = "#{path_was}#{counter}"
+
+            while route_exists?(join_routable_path(namespace_path, path))
+              counter += 1
+              path = "#{path_was}#{counter}"
+            end
+
+            path
+          end
+
+          def remove_last_occurrence(string, pattern)
+            string.reverse.sub(pattern.reverse, "").reverse
+          end
+
+          def join_routable_path(namespace_path, top_level)
+            if namespace_path.present?
+              File.join(namespace_path, top_level)
+            else
+              top_level
+            end
+          end
+
+          def route_exists?(full_path)
+            MigrationClasses::Route.where(Route.arel_table[:path].matches(full_path)).any?
+          end
+
+          def move_pages(old_path, new_path)
+            move_folders(pages_dir, old_path, new_path)
+          end
+
+          def move_uploads(old_path, new_path)
+            return unless file_storage?
+
+            move_folders(uploads_dir, old_path, new_path)
+          end
+
+          def move_folders(directory, old_relative_path, new_relative_path)
+            old_path = File.join(directory, old_relative_path)
+            return unless File.directory?(old_path)
+
+            new_path = File.join(directory, new_relative_path)
+            FileUtils.mv(old_path, new_path)
+          end
+
+          def remove_cached_html_for_projects(project_ids)
+            update_column_in_batches(:projects, :description_html, nil) do |table, query|
+              query.where(table[:id].in(project_ids))
+            end
+
+            update_column_in_batches(:issues, :description_html, nil) do |table, query|
+              query.where(table[:project_id].in(project_ids))
+            end
+
+            update_column_in_batches(:merge_requests, :description_html, nil) do |table, query|
+              query.where(table[:target_project_id].in(project_ids))
+            end
+
+            update_column_in_batches(:notes, :note_html, nil) do |table, query|
+              query.where(table[:project_id].in(project_ids))
+            end
+
+            update_column_in_batches(:milestones, :description_html, nil) do |table, query|
+              query.where(table[:project_id].in(project_ids))
+            end
+          end
+
+          def file_storage?
+            CarrierWave::Uploader::Base.storage == CarrierWave::Storage::File
+          end
+
+          def uploads_dir
+            File.join(CarrierWave.root, "uploads")
+          end
+
+          def pages_dir
+            Settings.pages.path
+          end
+        end
+      end
+    end
+  end
+end
diff --git a/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_namespaces.rb b/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_namespaces.rb
new file mode 100644
index 0000000000000000000000000000000000000000..b9f4f3cff3cb5900b4b490fc58bcb9187465c80d
--- /dev/null
+++ b/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_namespaces.rb
@@ -0,0 +1,72 @@
+module Gitlab
+  module Database
+    module RenameReservedPathsMigration
+      module V1
+        class RenameNamespaces < RenameBase
+          include Gitlab::ShellAdapter
+
+          def rename_namespaces(type:)
+            namespaces_for_paths(type: type).each do |namespace|
+              rename_namespace(namespace)
+            end
+          end
+
+          def namespaces_for_paths(type:)
+            namespaces = case type
+                         when :child
+                           MigrationClasses::Namespace.where.not(parent_id: nil)
+                         when :top_level
+                           MigrationClasses::Namespace.where(parent_id: nil)
+                         end
+            with_paths = MigrationClasses::Route.arel_table[:path].
+                           matches_any(path_patterns)
+            namespaces.joins(:route).where(with_paths)
+          end
+
+          def rename_namespace(namespace)
+            old_full_path, new_full_path = rename_path_for_routable(namespace)
+
+            move_repositories(namespace, old_full_path, new_full_path)
+            move_uploads(old_full_path, new_full_path)
+            move_pages(old_full_path, new_full_path)
+            remove_cached_html_for_projects(projects_for_namespace(namespace).map(&:id))
+          end
+
+          def move_repositories(namespace, old_full_path, new_full_path)
+            repo_paths_for_namespace(namespace).each do |repository_storage_path|
+              # Ensure old directory exists before moving it
+              gitlab_shell.add_namespace(repository_storage_path, old_full_path)
+
+              unless gitlab_shell.mv_namespace(repository_storage_path, old_full_path, new_full_path)
+                message = "Exception moving path #{repository_storage_path} \
+                           from #{old_full_path} to #{new_full_path}"
+                Rails.logger.error message
+              end
+            end
+          end
+
+          def repo_paths_for_namespace(namespace)
+            projects_for_namespace(namespace).distinct.select(:repository_storage).
+              map(&:repository_storage_path)
+          end
+
+          def projects_for_namespace(namespace)
+            namespace_ids = child_ids_for_parent(namespace, ids: [namespace.id])
+            namespace_or_children = MigrationClasses::Project.
+                                      arel_table[:namespace_id].
+                                      in(namespace_ids)
+            MigrationClasses::Project.where(namespace_or_children)
+          end
+
+          def child_ids_for_parent(namespace, ids: [])
+            namespace.children.each do |child|
+              ids << child.id
+              child_ids_for_parent(child, ids: ids) if child.children.any?
+            end
+            ids
+          end
+        end
+      end
+    end
+  end
+end
diff --git a/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_projects.rb b/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_projects.rb
new file mode 100644
index 0000000000000000000000000000000000000000..448717eb744d6c5e143721ce286967da80faa845
--- /dev/null
+++ b/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_projects.rb
@@ -0,0 +1,45 @@
+module Gitlab
+  module Database
+    module RenameReservedPathsMigration
+      module V1
+        class RenameProjects < RenameBase
+          include Gitlab::ShellAdapter
+
+          def rename_projects
+            projects_for_paths.each do |project|
+              rename_project(project)
+            end
+
+            remove_cached_html_for_projects(projects_for_paths.map(&:id))
+          end
+
+          def rename_project(project)
+            old_full_path, new_full_path = rename_path_for_routable(project)
+
+            move_repository(project, old_full_path, new_full_path)
+            move_repository(project, "#{old_full_path}.wiki", "#{new_full_path}.wiki")
+            move_uploads(old_full_path, new_full_path)
+            move_pages(old_full_path, new_full_path)
+          end
+
+          def move_repository(project, old_path, new_path)
+            unless gitlab_shell.mv_repository(project.repository_storage_path,
+                                              old_path,
+                                              new_path)
+              Rails.logger.error "Error moving #{old_path} to #{new_path}"
+            end
+          end
+
+          def projects_for_paths
+            return @projects_for_paths if @projects_for_paths
+
+            with_paths = MigrationClasses::Route.arel_table[:path]
+                           .matches_any(path_patterns)
+
+            @projects_for_paths = MigrationClasses::Project.joins(:route).where(with_paths)
+          end
+        end
+      end
+    end
+  end
+end
diff --git a/lib/gitlab/etag_caching/router.rb b/lib/gitlab/etag_caching/router.rb
index f6e4f279c06d1b6ebee1f4d58b776ec7e9dfe271..aac210f19e8385e15a2747f9842e55017d7a4996 100644
--- a/lib/gitlab/etag_caching/router.rb
+++ b/lib/gitlab/etag_caching/router.rb
@@ -2,31 +2,39 @@ module Gitlab
   module EtagCaching
     class Router
       Route = Struct.new(:regexp, :name)
-
-      RESERVED_WORDS = NamespaceValidator::WILDCARD_ROUTES.map { |word| "/#{word}/" }.join('|')
+      # We enable an ETag for every request matching the regex.
+      # To match a regex the path needs to match the following:
+      #   - Don't contain a reserved word (expect for the words used in the
+      #     regex itself)
+      #   - Ending in `noteable/issue/<id>/notes` for the `issue_notes` route
+      #   - Ending in `issues/id`/rendered_title` for the `issue_title` route
+      USED_IN_ROUTES = %w[noteable issue notes issues rendered_title
+                          commit pipelines merge_requests new].freeze
+      RESERVED_WORDS = DynamicPathValidator::WILDCARD_ROUTES - USED_IN_ROUTES
+      RESERVED_WORDS_REGEX = Regexp.union(*RESERVED_WORDS)
       ROUTES = [
         Gitlab::EtagCaching::Router::Route.new(
-          %r(^(?!.*(#{RESERVED_WORDS})).*/noteable/issue/\d+/notes\z),
+          %r(^(?!.*(#{RESERVED_WORDS_REGEX})).*/noteable/issue/\d+/notes\z),
           'issue_notes'
         ),
         Gitlab::EtagCaching::Router::Route.new(
-          %r(^(?!.*(#{RESERVED_WORDS})).*/issues/\d+/rendered_title\z),
+          %r(^(?!.*(#{RESERVED_WORDS_REGEX})).*/issues/\d+/rendered_title\z),
           'issue_title'
         ),
         Gitlab::EtagCaching::Router::Route.new(
-          %r(^(?!.*(#{RESERVED_WORDS})).*/commit/\S+/pipelines\.json\z),
+          %r(^(?!.*(#{RESERVED_WORDS_REGEX})).*/commit/\S+/pipelines\.json\z),
           'commit_pipelines'
         ),
         Gitlab::EtagCaching::Router::Route.new(
-          %r(^(?!.*(#{RESERVED_WORDS})).*/merge_requests/new\.json\z),
+          %r(^(?!.*(#{RESERVED_WORDS_REGEX})).*/merge_requests/new\.json\z),
           'new_merge_request_pipelines'
         ),
         Gitlab::EtagCaching::Router::Route.new(
-          %r(^(?!.*(#{RESERVED_WORDS})).*/merge_requests/\d+/pipelines\.json\z),
+          %r(^(?!.*(#{RESERVED_WORDS_REGEX})).*/merge_requests/\d+/pipelines\.json\z),
           'merge_request_pipelines'
         ),
         Gitlab::EtagCaching::Router::Route.new(
-          %r(^(?!.*(#{RESERVED_WORDS})).*/pipelines\.json\z),
+          %r(^(?!.*(#{RESERVED_WORDS_REGEX})).*/pipelines\.json\z),
           'project_pipelines'
         )
       ].freeze
diff --git a/lib/gitlab/git/repository.rb b/lib/gitlab/git/repository.rb
index 18eda0279f76b18834b6d40fe65dfc20e5a62bf5..acd0037ee4f3b153ff9f20fbeee722babe70744f 100644
--- a/lib/gitlab/git/repository.rb
+++ b/lib/gitlab/git/repository.rb
@@ -45,17 +45,13 @@ module Gitlab
 
       # Default branch in the repository
       def root_ref
-        # NOTE: This feature is intentionally disabled until
-        # https://gitlab.com/gitlab-org/gitaly/issues/179 is resolved
-        # @root_ref ||= Gitlab::GitalyClient.migrate(:root_ref) do |is_enabled|
-        #   if is_enabled
-        #     gitaly_ref_client.default_branch_name
-        #   else
-        @root_ref ||= discover_default_branch
-        #   end
-        # end
-      rescue GRPC::BadStatus => e
-        raise CommandError.new(e)
+        @root_ref ||= gitaly_migrate(:root_ref) do |is_enabled|
+          if is_enabled
+            gitaly_ref_client.default_branch_name
+          else
+            discover_default_branch
+          end
+        end
       end
 
       # Alias to old method for compatibility
@@ -72,17 +68,13 @@ module Gitlab
       # Returns an Array of branch names
       # sorted by name ASC
       def branch_names
-        # Gitlab::GitalyClient.migrate(:branch_names) do |is_enabled|
-        #   NOTE: This feature is intentionally disabled until
-        #   https://gitlab.com/gitlab-org/gitaly/issues/179 is resolved
-        #   if is_enabled
-        #     gitaly_ref_client.branch_names
-        #   else
-        branches.map(&:name)
-        #   end
-        # end
-      rescue GRPC::BadStatus => e
-        raise CommandError.new(e)
+        gitaly_migrate(:branch_names) do |is_enabled|
+          if is_enabled
+            gitaly_ref_client.branch_names
+          else
+            branches.map(&:name)
+          end
+        end
       end
 
       # Returns an Array of Branches
@@ -122,30 +114,43 @@ module Gitlab
 
       # Returns the number of valid branches
       def branch_count
-        rugged.branches.count do |ref|
-          begin
-            ref.name && ref.target # ensures the branch is valid
+        Gitlab::GitalyClient.migrate(:branch_names) do |is_enabled|
+          if is_enabled
+            gitaly_ref_client.count_branch_names
+          else
+            rugged.branches.count do |ref|
+              begin
+                ref.name && ref.target # ensures the branch is valid
 
-            true
-          rescue Rugged::ReferenceError
-            false
+                true
+              rescue Rugged::ReferenceError
+                false
+              end
+            end
+          end
+        end
+      end
+
+      # Returns the number of valid tags
+      def tag_count
+        Gitlab::GitalyClient.migrate(:tag_names) do |is_enabled|
+          if is_enabled
+            gitaly_ref_client.count_tag_names
+          else
+            rugged.tags.count
           end
         end
       end
 
       # Returns an Array of tag names
       def tag_names
-        # Gitlab::GitalyClient.migrate(:tag_names) do |is_enabled|
-        #   NOTE: This feature is intentionally disabled until
-        #   https://gitlab.com/gitlab-org/gitaly/issues/179 is resolved
-        #   if is_enabled
-        #     gitaly_ref_client.tag_names
-        #   else
-        rugged.tags.map { |t| t.name }
-        #   end
-        # end
-      rescue GRPC::BadStatus => e
-        raise CommandError.new(e)
+        gitaly_migrate(:tag_names) do |is_enabled|
+          if is_enabled
+            gitaly_ref_client.tag_names
+          else
+            rugged.tags.map { |t| t.name }
+          end
+        end
       end
 
       # Returns an Array of Tags
@@ -1277,6 +1282,14 @@ module Gitlab
         @gitaly_commit_client ||= Gitlab::GitalyClient::Commit.new(self)
       end
 
+      def gitaly_migrate(method, &block)
+        Gitlab::GitalyClient.migrate(method, &block)
+      rescue GRPC::NotFound => e
+        raise NoRepository.new(e)
+      rescue GRPC::BadStatus => e
+        raise CommandError.new(e)
+      end
+
       # Returns the `Rugged` sorting type constant for a given
       # sort type key. Valid keys are `:none`, `:topo`, and `:date`
       def rugged_sort_type(key)
diff --git a/lib/gitlab/gitaly_client/ref.rb b/lib/gitlab/gitaly_client/ref.rb
index d3c0743db4e7d9f44c9c63136129b4557c5e5713..f6c77ef1a3e50b09cf718e0736aa4e3f1e06c178 100644
--- a/lib/gitlab/gitaly_client/ref.rb
+++ b/lib/gitlab/gitaly_client/ref.rb
@@ -11,7 +11,9 @@ module Gitlab
 
       def default_branch_name
         request = Gitaly::FindDefaultBranchNameRequest.new(repository: @gitaly_repo)
-        stub.find_default_branch_name(request).name.gsub(/^refs\/heads\//, '')
+        branch_name = stub.find_default_branch_name(request).name
+
+        Gitlab::Git.branch_name(branch_name)
       end
 
       def branch_names
@@ -34,6 +36,14 @@ module Gitlab
         stub.find_ref_name(request).name
       end
 
+      def count_tag_names
+        tag_names.count
+      end
+
+      def count_branch_names
+        branch_names.count
+      end
+
       private
 
       def consume_refs_response(response, prefix:)
diff --git a/lib/gitlab/import_export/import_export.yml b/lib/gitlab/import_export/import_export.yml
index 899a656776812c8ada3707d708f7311debec5453..b95cea371b9d0632fcec289cbac43f680c4012fe 100644
--- a/lib/gitlab/import_export/import_export.yml
+++ b/lib/gitlab/import_export/import_export.yml
@@ -41,7 +41,6 @@ project_tree:
     - :statuses
   - triggers:
     - :trigger_schedule
-  - :deploy_keys
   - :services
   - :hooks
   - protected_branches:
@@ -53,10 +52,6 @@ project_tree:
 
 # Only include the following attributes for the models specified.
 included_attributes:
-  project:
-    - :description
-    - :visibility_level
-    - :archived
   user:
     - :id
     - :email
@@ -66,6 +61,29 @@ included_attributes:
 
 # Do not include the following attributes for the models specified.
 excluded_attributes:
+  project:
+    - :name
+    - :path
+    - :namespace_id
+    - :creator_id
+    - :import_url
+    - :import_status
+    - :avatar
+    - :import_type
+    - :import_source
+    - :import_error
+    - :mirror
+    - :runners_token
+    - :repository_storage
+    - :repository_read_only
+    - :lfs_enabled
+    - :import_jid
+    - :created_at
+    - :updated_at
+    - :import_jid
+    - :import_jid
+    - :id
+    - :star_count
   snippets:
     - :expired_at
   merge_request_diff:
@@ -94,3 +112,5 @@ methods:
     - :utf8_st_diffs
   merge_requests:
     - :diff_head_sha
+  project:
+    - :description_html
\ No newline at end of file
diff --git a/lib/gitlab/import_export/project_tree_restorer.rb b/lib/gitlab/import_export/project_tree_restorer.rb
index 2e349b5f9a997f8a3e18d1298ceb68540a5f7b04..84ab1977dfa87de5c82ba6cbc3651119824657a3 100644
--- a/lib/gitlab/import_export/project_tree_restorer.rb
+++ b/lib/gitlab/import_export/project_tree_restorer.rb
@@ -71,14 +71,14 @@ module Gitlab
       def restore_project
         return @project unless @tree_hash
 
-        @project.update(project_params)
+        @project.update_columns(project_params)
         @project
       end
 
       def project_params
         @tree_hash.reject do |key, value|
           # return params that are not 1 to many or 1 to 1 relations
-          value.is_a?(Array) || key == key.singularize
+          value.respond_to?(:each) && !Project.column_names.include?(key)
         end
       end
 
diff --git a/lib/gitlab/import_export/reader.rb b/lib/gitlab/import_export/reader.rb
index a1e7159fe42cce2f95c30e6f10b9f2d76bc6cb76..eb7f51205921b06fb1c6436dedbd109f251f6e46 100644
--- a/lib/gitlab/import_export/reader.rb
+++ b/lib/gitlab/import_export/reader.rb
@@ -15,7 +15,10 @@ module Gitlab
       # Outputs a hash in the format described here: http://api.rubyonrails.org/classes/ActiveModel/Serializers/JSON.html
       # for outputting a project in JSON format, including its relations and sub relations.
       def project_tree
-        @attributes_finder.find_included(:project).merge(include: build_hash(@tree))
+        attributes = @attributes_finder.find(:project)
+        project_attributes = attributes.is_a?(Hash) ? attributes[:project] : {}
+
+        project_attributes.merge(include: build_hash(@tree))
       rescue => e
         @shared.error(e)
         false
diff --git a/lib/gitlab/regex.rb b/lib/gitlab/regex.rb
index 08b061d5e311469d5c157e3f96d4bfba606d2f01..b7fef5dd0683cc12e45e438997cb5bdf349c2b65 100644
--- a/lib/gitlab/regex.rb
+++ b/lib/gitlab/regex.rb
@@ -22,6 +22,10 @@ module Gitlab
       @namespace_regex ||= /\A#{NAMESPACE_REGEX_STR}\z/.freeze
     end
 
+    def full_namespace_regex
+      @full_namespace_regex ||= %r{\A#{FULL_NAMESPACE_REGEX_STR}\z}
+    end
+
     def namespace_route_regex
       @namespace_route_regex ||= /#{NAMESPACE_REGEX_STR}/.freeze
     end
diff --git a/lib/tasks/gitlab/gitaly.rake b/lib/tasks/gitlab/gitaly.rake
index 046780481baa1288786d5c896dd6356940cee289..3c5bc0146a1e906a548da2716740788a95c3a9d3 100644
--- a/lib/tasks/gitlab/gitaly.rake
+++ b/lib/tasks/gitlab/gitaly.rake
@@ -1,18 +1,18 @@
 namespace :gitlab do
   namespace :gitaly do
     desc "GitLab | Install or upgrade gitaly"
-    task :install, [:dir] => :environment do |t, args|
+    task :install, [:dir, :repo] => :environment do |t, args|
       require 'toml'
 
       warn_user_is_not_gitlab
       unless args.dir.present?
         abort %(Please specify the directory where you want to install gitaly:\n  rake "gitlab:gitaly:install[/home/git/gitaly]")
       end
+      args.with_defaults(repo: 'https://gitlab.com/gitlab-org/gitaly.git')
 
       version = Gitlab::GitalyClient.expected_server_version
-      repo = 'https://gitlab.com/gitlab-org/gitaly.git'
 
-      checkout_or_clone_version(version: version, repo: repo, target_dir: args.dir)
+      checkout_or_clone_version(version: version, repo: args.repo, target_dir: args.dir)
 
       _, status = Gitlab::Popen.popen(%w[which gmake])
       command = status.zero? ? 'gmake' : 'make'
diff --git a/lib/tasks/gitlab/workhorse.rake b/lib/tasks/gitlab/workhorse.rake
index a00b02188cfa26e2bd12c2b43b46b4f5d6f9d585..e7ac0b5859f1ad39656ab6647a953c74373cfdf3 100644
--- a/lib/tasks/gitlab/workhorse.rake
+++ b/lib/tasks/gitlab/workhorse.rake
@@ -1,16 +1,16 @@
 namespace :gitlab do
   namespace :workhorse do
     desc "GitLab | Install or upgrade gitlab-workhorse"
-    task :install, [:dir] => :environment do |t, args|
+    task :install, [:dir, :repo] => :environment do |t, args|
       warn_user_is_not_gitlab
       unless args.dir.present?
         abort %(Please specify the directory where you want to install gitlab-workhorse:\n  rake "gitlab:workhorse:install[/home/git/gitlab-workhorse]")
       end
+      args.with_defaults(repo: 'https://gitlab.com/gitlab-org/gitlab-workhorse.git')
 
       version = Gitlab::Workhorse.version
-      repo = 'https://gitlab.com/gitlab-org/gitlab-workhorse.git'
 
-      checkout_or_clone_version(version: version, repo: repo, target_dir: args.dir)
+      checkout_or_clone_version(version: version, repo: args.repo, target_dir: args.dir)
 
       _, status = Gitlab::Popen.popen(%w[which gmake])
       command = status.zero? ? 'gmake' : 'make'
diff --git a/scripts/notify_slack.sh b/scripts/notify_slack.sh
deleted file mode 100755
index 6b3bc563c7a34070b5fbf1d346428369b8db421a..0000000000000000000000000000000000000000
--- a/scripts/notify_slack.sh
+++ /dev/null
@@ -1,13 +0,0 @@
-#!/bin/sh
-# Sends Slack notification ERROR_MSG to CHANNEL
-# An env. variable CI_SLACK_WEBHOOK_URL needs to be set.
-
-CHANNEL=$1
-ERROR_MSG=$2
-
-if [ -z "$CHANNEL" ] || [ -z "$ERROR_MSG" ] || [ -z "$CI_SLACK_WEBHOOK_URL" ]; then
-    echo "Missing argument(s) - Use: $0 channel message"
-    echo "and set CI_SLACK_WEBHOOK_URL environment variable."
-else
-    curl -X POST --data-urlencode 'payload={"channel": "'"$CHANNEL"'", "username": "gitlab-ci", "text": "'"$ERROR_MSG"'", "icon_emoji": ":gitlab:"}' "$CI_SLACK_WEBHOOK_URL"
-fi
\ No newline at end of file
diff --git a/spec/controllers/application_controller_spec.rb b/spec/controllers/application_controller_spec.rb
index 760f33b09c17692d0fcc93d463a0ee68b8c3a918..1bf0533ca24871f26efbc3e46ace7889bbe61f8e 100644
--- a/spec/controllers/application_controller_spec.rb
+++ b/spec/controllers/application_controller_spec.rb
@@ -4,7 +4,7 @@ describe ApplicationController do
   let(:user) { create(:user) }
 
   describe '#check_password_expiration' do
-    let(:controller) { ApplicationController.new }
+    let(:controller) { described_class.new }
 
     it 'redirects if the user is over their password expiry' do
       user.password_expires_at = Time.new(2002)
@@ -34,7 +34,7 @@ describe ApplicationController do
 
   describe "#authenticate_user_from_token!" do
     describe "authenticating a user from a private token" do
-      controller(ApplicationController) do
+      controller(described_class) do
         def index
           render text: "authenticated"
         end
@@ -66,7 +66,7 @@ describe ApplicationController do
     end
 
     describe "authenticating a user from a personal access token" do
-      controller(ApplicationController) do
+      controller(described_class) do
         def index
           render text: 'authenticated'
         end
@@ -115,7 +115,7 @@ describe ApplicationController do
   end
 
   context 'two-factor authentication' do
-    let(:controller) { ApplicationController.new }
+    let(:controller) { described_class.new }
 
     describe '#check_two_factor_requirement' do
       subject { controller.send :check_two_factor_requirement }
diff --git a/spec/controllers/projects/deployments_controller_spec.rb b/spec/controllers/projects/deployments_controller_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..89692b601b2e3135f31ab8243fa7f35e1e103f37
--- /dev/null
+++ b/spec/controllers/projects/deployments_controller_spec.rb
@@ -0,0 +1,42 @@
+require 'spec_helper'
+
+describe Projects::DeploymentsController do
+  include ApiHelpers
+
+  let(:user) { create(:user) }
+  let(:project) { create(:empty_project) }
+  let(:environment) { create(:environment, name: 'production', project: project) }
+
+  before do
+    project.add_master(user)
+
+    sign_in(user)
+  end
+
+  describe 'GET #index' do
+    it 'returns list of deployments from last 8 hours' do
+      create(:deployment, environment: environment, created_at: 9.hours.ago)
+      create(:deployment, environment: environment, created_at: 7.hours.ago)
+      create(:deployment, environment: environment)
+
+      get :index, environment_params(after: 8.hours.ago)
+
+      expect(response).to be_ok
+
+      expect(json_response['deployments'].count).to eq(2)
+    end
+
+    it 'returns a list with deployments information' do
+      create(:deployment, environment: environment)
+
+      get :index, environment_params
+
+      expect(response).to be_ok
+      expect(response).to match_response_schema('deployments')
+    end
+  end
+
+  def environment_params(opts = {})
+    opts.reverse_merge(namespace_id: project.namespace, project_id: project, environment_id: environment.id)
+  end
+end
diff --git a/spec/controllers/projects/notes_controller_spec.rb b/spec/controllers/projects/notes_controller_spec.rb
index f140eaef5d570c3ddbbb36428cc98ab625a3d15c..45f4cf9180db89ba537fb207a794c6883e70773c 100644
--- a/spec/controllers/projects/notes_controller_spec.rb
+++ b/spec/controllers/projects/notes_controller_spec.rb
@@ -167,6 +167,47 @@ describe Projects::NotesController do
     end
   end
 
+  describe 'DELETE destroy' do
+    let(:request_params) do
+      {
+        namespace_id: project.namespace,
+        project_id: project,
+        id: note,
+        format: :js
+      }
+    end
+
+    context 'user is the author of a note' do
+      before do
+        sign_in(note.author)
+        project.team << [note.author, :developer]
+      end
+
+      it "returns status 200 for html" do
+        delete :destroy, request_params
+
+        expect(response).to have_http_status(200)
+      end
+
+      it "deletes the note" do
+        expect { delete :destroy, request_params }.to change { Note.count }.from(1).to(0)
+      end
+    end
+
+    context 'user is not the author of a note' do
+      before do
+        sign_in(user)
+        project.team << [user, :developer]
+      end
+
+      it "returns status 404" do
+        delete :destroy, request_params
+
+        expect(response).to have_http_status(404)
+      end
+    end
+  end
+
   describe 'POST toggle_award_emoji' do
     before do
       sign_in(user)
diff --git a/spec/controllers/snippets/notes_controller_spec.rb b/spec/controllers/snippets/notes_controller_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..1c494b8c7abc5a56878c3181e067110437a53350
--- /dev/null
+++ b/spec/controllers/snippets/notes_controller_spec.rb
@@ -0,0 +1,196 @@
+require 'spec_helper'
+
+describe Snippets::NotesController do
+  let(:user) { create(:user) }
+
+  let(:private_snippet)  { create(:personal_snippet, :private) }
+  let(:internal_snippet) { create(:personal_snippet, :internal) }
+  let(:public_snippet)   { create(:personal_snippet, :public) }
+
+  let(:note_on_private)  { create(:note_on_personal_snippet, noteable: private_snippet) }
+  let(:note_on_internal) { create(:note_on_personal_snippet, noteable: internal_snippet) }
+  let(:note_on_public)   { create(:note_on_personal_snippet, noteable: public_snippet) }
+
+  describe 'GET index' do
+    context 'when a snippet is public' do
+      before do
+        note_on_public
+
+        get :index, { snippet_id: public_snippet }
+      end
+
+      it "returns status 200" do
+        expect(response).to have_http_status(200)
+      end
+
+      it "returns not empty array of notes" do
+        expect(JSON.parse(response.body)["notes"].empty?).to be_falsey
+      end
+    end
+
+    context 'when a snippet is internal' do
+      before do
+        note_on_internal
+      end
+
+      context 'when user not logged in' do
+        it "returns status 404" do
+          get :index, { snippet_id: internal_snippet }
+
+          expect(response).to have_http_status(404)
+        end
+      end
+
+      context 'when user logged in' do
+        before do
+          sign_in(user)
+        end
+
+        it "returns status 200" do
+          get :index, { snippet_id: internal_snippet }
+
+          expect(response).to have_http_status(200)
+        end
+      end
+    end
+
+    context 'when a snippet is private' do
+      before do
+        note_on_private
+      end
+
+      context 'when user not logged in' do
+        it "returns status 404" do
+          get :index, { snippet_id: private_snippet }
+
+          expect(response).to have_http_status(404)
+        end
+      end
+
+      context 'when user other than author logged in' do
+        before do
+          sign_in(user)
+        end
+
+        it "returns status 404" do
+          get :index, { snippet_id: private_snippet }
+
+          expect(response).to have_http_status(404)
+        end
+      end
+
+      context 'when author logged in' do
+        before do
+          note_on_private
+
+          sign_in(private_snippet.author)
+        end
+
+        it "returns status 200" do
+          get :index, { snippet_id: private_snippet }
+
+          expect(response).to have_http_status(200)
+        end
+
+        it "returns 1 note" do
+          get :index, { snippet_id: private_snippet }
+
+          expect(JSON.parse(response.body)['notes'].count).to eq(1)
+        end
+      end
+    end
+
+    context 'dont show non visible notes' do
+      before do
+        note_on_public
+
+        sign_in(user)
+
+        expect_any_instance_of(Note).to receive(:cross_reference_not_visible_for?).and_return(true)
+      end
+
+      it "does not return any note" do
+        get :index, { snippet_id: public_snippet }
+
+        expect(JSON.parse(response.body)['notes'].count).to eq(0)
+      end
+    end
+  end
+
+  describe 'DELETE destroy' do
+    let(:request_params) do
+      {
+        snippet_id: public_snippet,
+        id: note_on_public,
+        format: :js
+      }
+    end
+
+    context 'when user is the author of a note' do
+      before do
+        sign_in(note_on_public.author)
+      end
+
+      it "returns status 200" do
+        delete :destroy, request_params
+
+        expect(response).to have_http_status(200)
+      end
+
+      it "deletes the note" do
+        expect{ delete :destroy, request_params }.to change{ Note.count }.from(1).to(0)
+      end
+
+      context 'system note' do
+        before do
+          expect_any_instance_of(Note).to receive(:system?).and_return(true)
+        end
+
+        it "does not delete the note" do
+          expect{ delete :destroy, request_params }.not_to change{ Note.count }
+        end
+      end
+    end
+
+    context 'when user is not the author of a note' do
+      before do
+        sign_in(user)
+
+        note_on_public
+      end
+
+      it "returns status 404" do
+        delete :destroy, request_params
+
+        expect(response).to have_http_status(404)
+      end
+
+      it "does not update the note" do
+        expect{ delete :destroy, request_params }.not_to change{ Note.count }
+      end
+    end
+  end
+
+  describe 'POST toggle_award_emoji' do
+    let(:note) { create(:note_on_personal_snippet, noteable: public_snippet) }
+    before do
+      sign_in(user)
+    end
+
+    subject { post(:toggle_award_emoji, snippet_id: public_snippet, id: note.id, name: "thumbsup") }
+
+    it "toggles the award emoji" do
+      expect { subject }.to change { note.award_emoji.count }.by(1)
+
+      expect(response).to have_http_status(200)
+    end
+
+    it "removes the already awarded emoji when it exists" do
+      note.toggle_award_emoji('thumbsup', user) # create award emoji before
+
+      expect { subject }.to change { AwardEmoji.count }.by(-1)
+
+      expect(response).to have_http_status(200)
+    end
+  end
+end
diff --git a/spec/controllers/snippets_controller_spec.rb b/spec/controllers/snippets_controller_spec.rb
index 234f3edd3d8c13eaf1d5fc3d11d065986a31ba8d..41cd5bdcdd8197b00dc51122532e5fc046579f19 100644
--- a/spec/controllers/snippets_controller_spec.rb
+++ b/spec/controllers/snippets_controller_spec.rb
@@ -350,144 +350,138 @@ describe SnippetsController do
     end
   end
 
-  %w(raw download).each do |action|
-    describe "GET #{action}" do
-      context 'when the personal snippet is private' do
-        let(:personal_snippet) { create(:personal_snippet, :private, author: user) }
+  describe "GET #raw" do
+    context 'when the personal snippet is private' do
+      let(:personal_snippet) { create(:personal_snippet, :private, author: user) }
 
-        context 'when signed in' do
-          before do
-            sign_in(user)
-          end
+      context 'when signed in' do
+        before do
+          sign_in(user)
+        end
 
-          context 'when signed in user is not the author' do
-            let(:other_author) { create(:author) }
-            let(:other_personal_snippet) { create(:personal_snippet, :private, author: other_author) }
+        context 'when signed in user is not the author' do
+          let(:other_author) { create(:author) }
+          let(:other_personal_snippet) { create(:personal_snippet, :private, author: other_author) }
 
-            it 'responds with status 404' do
-              get action, id: other_personal_snippet.to_param
+          it 'responds with status 404' do
+            get :raw, id: other_personal_snippet.to_param
 
-              expect(response).to have_http_status(404)
-            end
+            expect(response).to have_http_status(404)
           end
+        end
 
-          context 'when signed in user is the author' do
-            before { get action, id: personal_snippet.to_param }
+        context 'when signed in user is the author' do
+          before { get :raw, id: personal_snippet.to_param }
 
-            it 'responds with status 200' do
-              expect(assigns(:snippet)).to eq(personal_snippet)
-              expect(response).to have_http_status(200)
-            end
+          it 'responds with status 200' do
+            expect(assigns(:snippet)).to eq(personal_snippet)
+            expect(response).to have_http_status(200)
+          end
 
-            it 'has expected headers' do
-              expect(response.header['Content-Type']).to eq('text/plain; charset=utf-8')
+          it 'has expected headers' do
+            expect(response.header['Content-Type']).to eq('text/plain; charset=utf-8')
 
-              if action == :download
-                expect(response.header['Content-Disposition']).to match(/attachment/)
-              elsif action == :raw
-                expect(response.header['Content-Disposition']).to match(/inline/)
-              end
-            end
+            expect(response.header['Content-Disposition']).to match(/inline/)
           end
         end
+      end
 
-        context 'when not signed in' do
-          it 'redirects to the sign in page' do
-            get action, id: personal_snippet.to_param
+      context 'when not signed in' do
+        it 'redirects to the sign in page' do
+          get :raw, id: personal_snippet.to_param
 
-            expect(response).to redirect_to(new_user_session_path)
-          end
+          expect(response).to redirect_to(new_user_session_path)
         end
       end
+    end
 
-      context 'when the personal snippet is internal' do
-        let(:personal_snippet) { create(:personal_snippet, :internal, author: user) }
+    context 'when the personal snippet is internal' do
+      let(:personal_snippet) { create(:personal_snippet, :internal, author: user) }
 
-        context 'when signed in' do
-          before do
-            sign_in(user)
-          end
+      context 'when signed in' do
+        before do
+          sign_in(user)
+        end
 
-          it 'responds with status 200' do
-            get action, id: personal_snippet.to_param
+        it 'responds with status 200' do
+          get :raw, id: personal_snippet.to_param
 
-            expect(assigns(:snippet)).to eq(personal_snippet)
-            expect(response).to have_http_status(200)
-          end
+          expect(assigns(:snippet)).to eq(personal_snippet)
+          expect(response).to have_http_status(200)
         end
+      end
 
-        context 'when not signed in' do
-          it 'redirects to the sign in page' do
-            get action, id: personal_snippet.to_param
+      context 'when not signed in' do
+        it 'redirects to the sign in page' do
+          get :raw, id: personal_snippet.to_param
 
-            expect(response).to redirect_to(new_user_session_path)
-          end
+          expect(response).to redirect_to(new_user_session_path)
         end
       end
+    end
 
-      context 'when the personal snippet is public' do
-        let(:personal_snippet) { create(:personal_snippet, :public, author: user) }
+    context 'when the personal snippet is public' do
+      let(:personal_snippet) { create(:personal_snippet, :public, author: user) }
 
-        context 'when signed in' do
-          before do
-            sign_in(user)
-          end
+      context 'when signed in' do
+        before do
+          sign_in(user)
+        end
 
-          it 'responds with status 200' do
-            get action, id: personal_snippet.to_param
+        it 'responds with status 200' do
+          get :raw, id: personal_snippet.to_param
 
-            expect(assigns(:snippet)).to eq(personal_snippet)
-            expect(response).to have_http_status(200)
-          end
+          expect(assigns(:snippet)).to eq(personal_snippet)
+          expect(response).to have_http_status(200)
+        end
 
-          context 'CRLF line ending' do
-            let(:personal_snippet) do
-              create(:personal_snippet, :public, author: user, content: "first line\r\nsecond line\r\nthird line")
-            end
+        context 'CRLF line ending' do
+          let(:personal_snippet) do
+            create(:personal_snippet, :public, author: user, content: "first line\r\nsecond line\r\nthird line")
+          end
 
-            it 'returns LF line endings by default' do
-              get action, id: personal_snippet.to_param
+          it 'returns LF line endings by default' do
+            get :raw, id: personal_snippet.to_param
 
-              expect(response.body).to eq("first line\nsecond line\nthird line")
-            end
+            expect(response.body).to eq("first line\nsecond line\nthird line")
+          end
 
-            it 'does not convert line endings when parameter present' do
-              get action, id: personal_snippet.to_param, line_ending: :raw
+          it 'does not convert line endings when parameter present' do
+            get :raw, id: personal_snippet.to_param, line_ending: :raw
 
-              expect(response.body).to eq("first line\r\nsecond line\r\nthird line")
-            end
+            expect(response.body).to eq("first line\r\nsecond line\r\nthird line")
           end
         end
+      end
 
-        context 'when not signed in' do
-          it 'responds with status 200' do
-            get action, id: personal_snippet.to_param
+      context 'when not signed in' do
+        it 'responds with status 200' do
+          get :raw, id: personal_snippet.to_param
 
-            expect(assigns(:snippet)).to eq(personal_snippet)
-            expect(response).to have_http_status(200)
-          end
+          expect(assigns(:snippet)).to eq(personal_snippet)
+          expect(response).to have_http_status(200)
         end
       end
+    end
 
-      context 'when the personal snippet does not exist' do
-        context 'when signed in' do
-          before do
-            sign_in(user)
-          end
+    context 'when the personal snippet does not exist' do
+      context 'when signed in' do
+        before do
+          sign_in(user)
+        end
 
-          it 'responds with status 404' do
-            get action, id: 'doesntexist'
+        it 'responds with status 404' do
+          get :raw, id: 'doesntexist'
 
-            expect(response).to have_http_status(404)
-          end
+          expect(response).to have_http_status(404)
         end
+      end
 
-        context 'when not signed in' do
-          it 'responds with status 404' do
-            get action, id: 'doesntexist'
+      context 'when not signed in' do
+        it 'responds with status 404' do
+          get :raw, id: 'doesntexist'
 
-            expect(response).to have_http_status(404)
-          end
+          expect(response).to have_http_status(404)
         end
       end
     end
diff --git a/spec/factories/notes.rb b/spec/factories/notes.rb
index 93f4903119c41a0c5bd0c149273da60361604b0e..44c3186d8138d4b26aff0693d21dff7ba922a735 100644
--- a/spec/factories/notes.rb
+++ b/spec/factories/notes.rb
@@ -5,7 +5,7 @@ include ActionDispatch::TestProcess
 FactoryGirl.define do
   factory :note do
     project factory: :empty_project
-    note "Note"
+    note { generate(:title) }
     author
     on_issue
 
diff --git a/spec/factories/project_hooks.rb b/spec/factories/project_hooks.rb
index 39c2a9dd1fb621acab91716f47d7c5c4eaed1249..0210e871a635fdc0b0bcb16e3f746d4f02bdf4ac 100644
--- a/spec/factories/project_hooks.rb
+++ b/spec/factories/project_hooks.rb
@@ -1,6 +1,7 @@
 FactoryGirl.define do
   factory :project_hook do
     url { generate(:url) }
+    enable_ssl_verification false
 
     trait :token do
       token { SecureRandom.hex(10) }
@@ -11,6 +12,7 @@ FactoryGirl.define do
       merge_requests_events true
       tag_push_events true
       issues_events true
+      confidential_issues_events true
       note_events true
       build_events true
       pipeline_events true
diff --git a/spec/features/admin/admin_hooks_spec.rb b/spec/features/admin/admin_hooks_spec.rb
index fb519a9bf122ace522006db4ca3b869c988ca2b8..c5f24d412d7802a090f9efcf67962f898a6a0f04 100644
--- a/spec/features/admin/admin_hooks_spec.rb
+++ b/spec/features/admin/admin_hooks_spec.rb
@@ -1,6 +1,6 @@
 require 'spec_helper'
 
-describe "Admin::Hooks", feature: true do
+describe 'Admin::Hooks', feature: true do
   before do
     @project = create(:project)
     login_as :admin
@@ -8,24 +8,24 @@ describe "Admin::Hooks", feature: true do
     @system_hook = create(:system_hook)
   end
 
-  describe "GET /admin/hooks" do
-    it "is ok" do
+  describe 'GET /admin/hooks' do
+    it 'is ok' do
       visit admin_root_path
 
-      page.within ".layout-nav" do
-        click_on "Hooks"
+      page.within '.layout-nav' do
+        click_on 'Hooks'
       end
 
       expect(current_path).to eq(admin_hooks_path)
     end
 
-    it "has hooks list" do
+    it 'has hooks list' do
       visit admin_hooks_path
       expect(page).to have_content(@system_hook.url)
     end
   end
 
-  describe "New Hook" do
+  describe 'New Hook' do
     let(:url) { generate(:url) }
 
     it 'adds new hook' do
@@ -40,11 +40,36 @@ describe "Admin::Hooks", feature: true do
     end
   end
 
-  describe "Test" do
+  describe 'Update existing hook' do
+    let(:new_url) { generate(:url) }
+
+    it 'updates existing hook' do
+      visit admin_hooks_path
+
+      click_link 'Edit'
+      fill_in 'hook_url', with: new_url
+      check 'Enable SSL verification'
+      click_button 'Save changes'
+
+      expect(page).to have_content 'SSL Verification: enabled'
+      expect(current_path).to eq(admin_hooks_path)
+      expect(page).to have_content(new_url)
+    end
+  end
+
+  describe 'Remove existing hook' do
+    it 'remove existing hook' do
+      visit admin_hooks_path
+
+      expect { click_link 'Remove' }.to change(SystemHook, :count).by(-1)
+    end
+  end
+
+  describe 'Test' do
     before do
       WebMock.stub_request(:post, @system_hook.url)
       visit admin_hooks_path
-      click_link "Test hook"
+      click_link 'Test hook'
     end
 
     it { expect(current_path).to eq(admin_hooks_path) }
diff --git a/spec/features/issues/form_spec.rb b/spec/features/issues/form_spec.rb
index 755992069ffe557efe370a4987eea3d7211364b7..21b8cf3add5c52f7c625ce14f255dc63c3791fdf 100644
--- a/spec/features/issues/form_spec.rb
+++ b/spec/features/issues/form_spec.rb
@@ -2,6 +2,7 @@ require 'rails_helper'
 
 describe 'New/edit issue', feature: true, js: true do
   include GitlabRoutingHelper
+  include ActionView::Helpers::JavaScriptHelper
 
   let!(:project)   { create(:project) }
   let!(:user)      { create(:user)}
@@ -105,6 +106,33 @@ describe 'New/edit issue', feature: true, js: true do
 
       expect(find('.js-label-select')).to have_content('Labels')
     end
+
+    it 'correctly updates the selected user when changing assignee' do
+      click_button 'Assignee'
+      page.within '.dropdown-menu-user' do
+        click_link user.name
+      end
+
+      expect(find('input[name="issue[assignee_id]"]', visible: false).value).to match(user.id.to_s)
+
+      click_button user.name
+
+      expect(find('.dropdown-menu-user a.is-active').first(:xpath, '..')['data-user-id']).to eq(user.id.to_s)
+
+      # check the ::before pseudo element to ensure checkmark icon is present
+      expect(before_for_selector('.dropdown-menu-selectable a.is-active')).not_to eq('')
+      expect(before_for_selector('.dropdown-menu-selectable a:not(.is-active)')).to eq('')
+
+      page.within '.dropdown-menu-user' do
+        click_link user2.name
+      end
+
+      expect(find('input[name="issue[assignee_id]"]', visible: false).value).to match(user2.id.to_s)
+
+      click_button user2.name
+
+      expect(find('.dropdown-menu-user a.is-active').first(:xpath, '..')['data-user-id']).to eq(user2.id.to_s)
+    end
   end
 
   context 'edit issue' do
@@ -154,4 +182,14 @@ describe 'New/edit issue', feature: true, js: true do
       end
     end
   end
+
+  def before_for_selector(selector)
+    js = <<-JS.strip_heredoc
+      (function(selector) {
+        var el = document.querySelector(selector);
+        return window.getComputedStyle(el, '::before').getPropertyValue('content');
+      })("#{escape_javascript(selector)}")
+    JS
+    page.evaluate_script(js)
+  end
 end
diff --git a/spec/features/projects/blobs/blob_show_spec.rb b/spec/features/projects/blobs/blob_show_spec.rb
index 6a6f8b4f4d51dbf5f3aa5c61c1772e830ca02c25..8dba2ccbafaedff4af6e82871e8c33efafe8c51c 100644
--- a/spec/features/projects/blobs/blob_show_spec.rb
+++ b/spec/features/projects/blobs/blob_show_spec.rb
@@ -231,7 +231,7 @@ feature 'File blob', :js, feature: true do
         branch_name: 'master',
         commit_message: "Add PDF",
         file_path: 'files/test.pdf',
-        file_content: File.read(Rails.root.join('spec/javascripts/blob/pdf/test.pdf'))
+        file_content: project.repository.blob_at('add-pdf-file', 'files/pdf/test.pdf').data
       ).execute
 
       visit_blob('files/test.pdf')
diff --git a/spec/features/projects/settings/integration_settings_spec.rb b/spec/features/projects/settings/integration_settings_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..7909234556e4255681bc125bb857ce721a9ea4b5
--- /dev/null
+++ b/spec/features/projects/settings/integration_settings_spec.rb
@@ -0,0 +1,94 @@
+require 'spec_helper'
+
+feature 'Integration settings', feature: true do
+  let(:project) { create(:empty_project) }
+  let(:user) { create(:user) }
+  let(:role) { :developer }
+  let(:integrations_path) { namespace_project_settings_integrations_path(project.namespace, project) }
+
+  background do
+    login_as(user)
+    project.team << [user, role]
+  end
+
+  context 'for developer' do
+    given(:role) { :developer }
+
+    scenario 'to be disallowed to view' do
+      visit integrations_path
+
+      expect(page.status_code).to eq(404)
+    end
+  end
+
+  context 'for master' do
+    given(:role) { :master }
+
+    context 'Webhooks' do
+      let(:hook) { create(:project_hook, :all_events_enabled, enable_ssl_verification: true, project: project) }
+      let(:url) { generate(:url) }
+
+      scenario 'show list of webhooks' do
+        hook
+
+        visit integrations_path
+
+        expect(page.status_code).to eq(200)
+        expect(page).to have_content(hook.url)
+        expect(page).to have_content('SSL Verification: enabled')
+        expect(page).to have_content('Push Events')
+        expect(page).to have_content('Tag Push Events')
+        expect(page).to have_content('Issues Events')
+        expect(page).to have_content('Confidential Issues Events')
+        expect(page).to have_content('Note Events')
+        expect(page).to have_content('Merge Requests  Events')
+        expect(page).to have_content('Pipeline Events')
+        expect(page).to have_content('Wiki Page Events')
+      end
+
+      scenario 'create webhook' do
+        visit integrations_path
+
+        fill_in 'hook_url', with: url
+        check 'Tag push events'
+        check 'Enable SSL verification'
+
+        click_button 'Add webhook'
+
+        expect(page).to have_content(url)
+        expect(page).to have_content('SSL Verification: enabled')
+        expect(page).to have_content('Push Events')
+        expect(page).to have_content('Tag Push Events')
+      end
+
+      scenario 'edit existing webhook' do
+        hook
+        visit integrations_path
+
+        click_link 'Edit'
+        fill_in 'hook_url', with: url
+        check 'Enable SSL verification'
+        click_button 'Save changes'
+
+        expect(page).to have_content 'SSL Verification: enabled'
+        expect(page).to have_content(url)
+      end
+
+      scenario 'test existing webhook' do
+        WebMock.stub_request(:post, hook.url)
+        visit integrations_path
+
+        click_link 'Test'
+
+        expect(current_path).to eq(integrations_path)
+      end
+
+      scenario 'remove existing webhook' do
+        hook
+        visit integrations_path
+
+        expect { click_link 'Remove' }.to change(ProjectHook, :count).by(-1)
+      end
+    end
+  end
+end
diff --git a/spec/features/projects/snippets/show_spec.rb b/spec/features/projects/snippets/show_spec.rb
index 7eb1210e3079870137a167a5ef8e4ba94e44d588..cedf3778c7ec3e8807ba2ea54e7da1e43ebffb3b 100644
--- a/spec/features/projects/snippets/show_spec.rb
+++ b/spec/features/projects/snippets/show_spec.rb
@@ -30,6 +30,12 @@ feature 'Project snippet', :js, feature: true do
 
         # shows an enabled copy button
         expect(page).to have_selector('.js-copy-blob-source-btn:not(.disabled)')
+
+        # shows a raw button
+        expect(page).to have_link('Open raw')
+
+        # shows a download button
+        expect(page).to have_link('Download')
       end
     end
   end
@@ -59,6 +65,12 @@ feature 'Project snippet', :js, feature: true do
 
           # shows a disabled copy button
           expect(page).to have_selector('.js-copy-blob-source-btn.disabled')
+
+          # shows a raw button
+          expect(page).to have_link('Open raw')
+
+          # shows a download button
+          expect(page).to have_link('Download')
         end
       end
 
diff --git a/spec/features/security/project/internal_access_spec.rb b/spec/features/security/project/internal_access_spec.rb
index a1a36931824d87b7738a0b46f90f34f0f6435885..26879a77c487e1c925f42441f0b029f1189108bb 100644
--- a/spec/features/security/project/internal_access_spec.rb
+++ b/spec/features/security/project/internal_access_spec.rb
@@ -466,6 +466,21 @@ describe "Internal Project Access", feature: true  do
     it { is_expected.to be_denied_for(:visitor) }
   end
 
+  describe "GET /:project_path/environments/:id/deployments" do
+    let(:environment) { create(:environment, project: project) }
+    subject { namespace_project_environment_deployments_path(project.namespace, project, environment) }
+
+    it { is_expected.to be_allowed_for(:admin) }
+    it { is_expected.to be_allowed_for(:owner).of(project) }
+    it { is_expected.to be_allowed_for(:master).of(project) }
+    it { is_expected.to be_allowed_for(:developer).of(project) }
+    it { is_expected.to be_allowed_for(:reporter).of(project) }
+    it { is_expected.to be_denied_for(:guest).of(project) }
+    it { is_expected.to be_denied_for(:user) }
+    it { is_expected.to be_denied_for(:external) }
+    it { is_expected.to be_denied_for(:visitor) }
+  end
+
   describe "GET /:project_path/environments/new" do
     subject { new_namespace_project_environment_path(project.namespace, project) }
 
diff --git a/spec/features/security/project/private_access_spec.rb b/spec/features/security/project/private_access_spec.rb
index 5d58494a22aa6a33da0283d99c12bae8a4b060e1..699ca4f724c4faa1f2b7a3feed3e30faa8a81577 100644
--- a/spec/features/security/project/private_access_spec.rb
+++ b/spec/features/security/project/private_access_spec.rb
@@ -449,6 +449,21 @@ describe "Private Project Access", feature: true  do
     it { is_expected.to be_denied_for(:visitor) }
   end
 
+  describe "GET /:project_path/environments/:id/deployments" do
+    let(:environment) { create(:environment, project: project) }
+    subject { namespace_project_environment_deployments_path(project.namespace, project, environment) }
+
+    it { is_expected.to be_allowed_for(:admin) }
+    it { is_expected.to be_allowed_for(:owner).of(project) }
+    it { is_expected.to be_allowed_for(:master).of(project) }
+    it { is_expected.to be_allowed_for(:developer).of(project) }
+    it { is_expected.to be_allowed_for(:reporter).of(project) }
+    it { is_expected.to be_denied_for(:guest).of(project) }
+    it { is_expected.to be_denied_for(:user) }
+    it { is_expected.to be_denied_for(:external) }
+    it { is_expected.to be_denied_for(:visitor) }
+  end
+
   describe "GET /:project_path/environments/new" do
     subject { new_namespace_project_environment_path(project.namespace, project) }
 
diff --git a/spec/features/security/project/public_access_spec.rb b/spec/features/security/project/public_access_spec.rb
index 5df5b710dc4083e403735339c689af1462e6d2ee..624f0d0f485908cdaab963dfb6d45b8dba5bd73b 100644
--- a/spec/features/security/project/public_access_spec.rb
+++ b/spec/features/security/project/public_access_spec.rb
@@ -286,6 +286,21 @@ describe "Public Project Access", feature: true  do
     it { is_expected.to be_denied_for(:visitor) }
   end
 
+  describe "GET /:project_path/environments/:id/deployments" do
+    let(:environment) { create(:environment, project: project) }
+    subject { namespace_project_environment_deployments_path(project.namespace, project, environment) }
+
+    it { is_expected.to be_allowed_for(:admin) }
+    it { is_expected.to be_allowed_for(:owner).of(project) }
+    it { is_expected.to be_allowed_for(:master).of(project) }
+    it { is_expected.to be_allowed_for(:developer).of(project) }
+    it { is_expected.to be_allowed_for(:reporter).of(project) }
+    it { is_expected.to be_denied_for(:guest).of(project) }
+    it { is_expected.to be_denied_for(:user) }
+    it { is_expected.to be_denied_for(:external) }
+    it { is_expected.to be_denied_for(:visitor) }
+  end
+
   describe "GET /:project_path/environments/new" do
     subject { new_namespace_project_environment_path(project.namespace, project) }
 
diff --git a/spec/features/snippets/notes_on_personal_snippets_spec.rb b/spec/features/snippets/notes_on_personal_snippets_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..c646039e0b1459d59007b09c681cc3c84b5c70f9
--- /dev/null
+++ b/spec/features/snippets/notes_on_personal_snippets_spec.rb
@@ -0,0 +1,39 @@
+require 'spec_helper'
+
+describe 'Comments on personal snippets', feature: true do
+  let!(:user)    { create(:user) }
+  let!(:snippet) { create(:personal_snippet, :public) }
+  let!(:snippet_notes) do
+    [
+      create(:note_on_personal_snippet, noteable: snippet, author: user),
+      create(:note_on_personal_snippet, noteable: snippet)
+    ]
+  end
+  let!(:other_note) { create(:note_on_personal_snippet) }
+
+  before do
+    login_as user
+    visit snippet_path(snippet)
+  end
+
+  subject { page }
+
+  context 'viewing the snippet detail page' do
+    it 'contains notes for a snippet with correct action icons' do
+      expect(page).to have_selector('#notes-list li', count: 2)
+
+      # comment authored by current user
+      page.within("#notes-list li#note_#{snippet_notes[0].id}") do
+        expect(page).to have_content(snippet_notes[0].note)
+        expect(page).to have_selector('.js-note-delete')
+        expect(page).to have_selector('.note-emoji-button')
+      end
+
+      page.within("#notes-list li#note_#{snippet_notes[1].id}") do
+        expect(page).to have_content(snippet_notes[1].note)
+        expect(page).not_to have_selector('.js-note-delete')
+        expect(page).to have_selector('.note-emoji-button')
+      end
+    end
+  end
+end
diff --git a/spec/features/snippets/show_spec.rb b/spec/features/snippets/show_spec.rb
index cebcba6a230771dab02b267a31777ea3ee54c0fa..e36cf547f803e1a044b0e20825d9652eb5c6e278 100644
--- a/spec/features/snippets/show_spec.rb
+++ b/spec/features/snippets/show_spec.rb
@@ -24,6 +24,12 @@ feature 'Snippet', :js, feature: true do
 
         # shows an enabled copy button
         expect(page).to have_selector('.js-copy-blob-source-btn:not(.disabled)')
+
+        # shows a raw button
+        expect(page).to have_link('Open raw')
+
+        # shows a download button
+        expect(page).to have_link('Download')
       end
     end
   end
@@ -53,6 +59,12 @@ feature 'Snippet', :js, feature: true do
 
           # shows a disabled copy button
           expect(page).to have_selector('.js-copy-blob-source-btn.disabled')
+
+          # shows a raw button
+          expect(page).to have_link('Open raw')
+
+          # shows a download button
+          expect(page).to have_link('Download')
         end
       end
 
diff --git a/spec/finders/issues_finder_spec.rb b/spec/finders/issues_finder_spec.rb
index a1ae1d746afbd935131c3f3b98253bfcf34582bf..a5f717e62338981f1fddc3f92e6d2aca56ac6bd4 100644
--- a/spec/finders/issues_finder_spec.rb
+++ b/spec/finders/issues_finder_spec.rb
@@ -16,7 +16,7 @@ describe IssuesFinder do
     set(:label_link) { create(:label_link, label: label, target: issue2) }
     let(:search_user) { user }
     let(:params) { {} }
-    let(:issues) { IssuesFinder.new(search_user, params.reverse_merge(scope: scope, state: 'opened')).execute }
+    let(:issues) { described_class.new(search_user, params.reverse_merge(scope: scope, state: 'opened')).execute }
 
     before(:context) do
       project1.team << [user, :master]
@@ -282,15 +282,15 @@ describe IssuesFinder do
     let!(:confidential_issue) { create(:issue, project: project, confidential: true) }
 
     it 'returns non confidential issues for nil user' do
-      expect(IssuesFinder.send(:not_restricted_by_confidentiality, nil)).to include(public_issue)
+      expect(described_class.send(:not_restricted_by_confidentiality, nil)).to include(public_issue)
     end
 
     it 'returns non confidential issues for user not authorized for the issues projects' do
-      expect(IssuesFinder.send(:not_restricted_by_confidentiality, user)).to include(public_issue)
+      expect(described_class.send(:not_restricted_by_confidentiality, user)).to include(public_issue)
     end
 
     it 'returns all issues for user authorized for the issues projects' do
-      expect(IssuesFinder.send(:not_restricted_by_confidentiality, authorized_user)).to include(public_issue, confidential_issue)
+      expect(described_class.send(:not_restricted_by_confidentiality, authorized_user)).to include(public_issue, confidential_issue)
     end
   end
 end
diff --git a/spec/finders/merge_requests_finder_spec.rb b/spec/finders/merge_requests_finder_spec.rb
index 21ef94ac5d1873c7ddefab851b362f31bcd72534..58b7cd5e09840f376151b67664086528b5ad51a7 100644
--- a/spec/finders/merge_requests_finder_spec.rb
+++ b/spec/finders/merge_requests_finder_spec.rb
@@ -23,26 +23,26 @@ describe MergeRequestsFinder do
   describe "#execute" do
     it 'filters by scope' do
       params = { scope: 'authored', state: 'opened' }
-      merge_requests = MergeRequestsFinder.new(user, params).execute
+      merge_requests = described_class.new(user, params).execute
       expect(merge_requests.size).to eq(3)
     end
 
     it 'filters by project' do
       params = { project_id: project1.id, scope: 'authored', state: 'opened' }
-      merge_requests = MergeRequestsFinder.new(user, params).execute
+      merge_requests = described_class.new(user, params).execute
       expect(merge_requests.size).to eq(1)
     end
 
     it 'filters by non_archived' do
       params = { non_archived: true }
-      merge_requests = MergeRequestsFinder.new(user, params).execute
+      merge_requests = described_class.new(user, params).execute
       expect(merge_requests.size).to eq(3)
     end
 
     it 'filters by iid' do
       params = { project_id: project1.id, iids: merge_request1.iid }
 
-      merge_requests = MergeRequestsFinder.new(user, params).execute
+      merge_requests = described_class.new(user, params).execute
 
       expect(merge_requests).to contain_exactly(merge_request1)
     end
diff --git a/spec/finders/notes_finder_spec.rb b/spec/finders/notes_finder_spec.rb
index 765bf44d863d171e193e6b4bc9933e4e3fa80ed0..ba6bbb3bce087ad975d998a39831e468f5b4af99 100644
--- a/spec/finders/notes_finder_spec.rb
+++ b/spec/finders/notes_finder_spec.rb
@@ -110,6 +110,15 @@ describe NotesFinder do
         expect(notes.count).to eq(1)
       end
 
+      it 'finds notes on personal snippets' do
+        note = create(:note_on_personal_snippet)
+        params = { target_type: 'personal_snippet', target_id: note.noteable_id }
+
+        notes = described_class.new(project, user, params).execute
+
+        expect(notes.count).to eq(1)
+      end
+
       it 'raises an exception for an invalid target_type' do
         params[:target_type] = 'invalid'
         expect { described_class.new(project, user, params).execute }.to raise_error('invalid target_type')
diff --git a/spec/finders/snippets_finder_spec.rb b/spec/finders/snippets_finder_spec.rb
index 975e99c58072713dec0ebd4a84a9cec9735dd831..cb6c80d1bd0ba4ab3484207f4ddd02ec697d2560 100644
--- a/spec/finders/snippets_finder_spec.rb
+++ b/spec/finders/snippets_finder_spec.rb
@@ -14,13 +14,13 @@ describe SnippetsFinder do
     let!(:snippet3) { create(:personal_snippet, :public) }
 
     it "returns all private and internal snippets" do
-      snippets = SnippetsFinder.new.execute(user, filter: :all)
+      snippets = described_class.new.execute(user, filter: :all)
       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)
+      snippets = described_class.new.execute(nil, filter: :all)
       expect(snippets).to include(snippet3)
       expect(snippets).not_to include(snippet1, snippet2)
     end
@@ -32,7 +32,7 @@ describe SnippetsFinder do
     let!(:snippet3) { create(:personal_snippet, :public) }
 
     it "returns public public snippets" do
-      snippets = SnippetsFinder.new.execute(nil, filter: :public)
+      snippets = described_class.new.execute(nil, filter: :public)
 
       expect(snippets).to include(snippet3)
       expect(snippets).not_to include(snippet1, snippet2)
@@ -45,36 +45,36 @@ describe SnippetsFinder do
     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)
+      snippets = described_class.new.execute(user1, filter: :by_user, user: user)
       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")
+      snippets = described_class.new.execute(user, filter: :by_user, user: user, scope: "are_internal")
       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")
+      snippets = described_class.new.execute(user, filter: :by_user, user: user, scope: "are_private")
       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")
+      snippets = described_class.new.execute(user, filter: :by_user, user: user, scope: "are_public")
       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)
+      snippets = described_class.new.execute(user, filter: :by_user, user: user)
       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)
+      snippets = described_class.new.execute(nil, filter: :by_user, user: user)
       expect(snippets).to include(snippet3)
       expect(snippets).not_to include(snippet2, snippet1)
     end
@@ -88,43 +88,43 @@ describe SnippetsFinder do
     end
 
     it "returns public snippets for unauthorized user" do
-      snippets = SnippetsFinder.new.execute(nil, filter: :by_project, project: project1)
+      snippets = described_class.new.execute(nil, filter: :by_project, project: project1)
       expect(snippets).to include(@snippet3)
       expect(snippets).not_to include(@snippet1, @snippet2)
     end
 
     it "returns public and internal snippets for non project members" do
-      snippets = SnippetsFinder.new.execute(user, filter: :by_project, project: project1)
+      snippets = described_class.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")
+      snippets = described_class.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")
+      snippets = described_class.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")
+      snippets = described_class.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)
+      snippets = described_class.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")
+      snippets = described_class.new.execute(user, filter: :by_project, project: project1, scope: "are_private")
       expect(snippets).to include(@snippet1)
     end
   end
diff --git a/spec/fixtures/api/schemas/deployments.json b/spec/fixtures/api/schemas/deployments.json
new file mode 100644
index 0000000000000000000000000000000000000000..1112f23aab25e1ab120f0ab91794432d2ebe82f3
--- /dev/null
+++ b/spec/fixtures/api/schemas/deployments.json
@@ -0,0 +1,58 @@
+{
+  "additionalProperties": false,
+  "properties": {
+    "deployments": {
+      "items": {
+        "additionalProperties": false,
+        "properties": {
+          "created_at": {
+            "type": "string"
+          },
+          "id": {
+            "type": "integer"
+          },
+          "iid": {
+            "type": "integer"
+          },
+          "last?": {
+            "type": "boolean"
+          },
+          "ref": {
+            "additionalProperties": false,
+            "properties": {
+              "name": {
+                "type": "string"
+              }
+            },
+            "required": [
+              "name"
+            ],
+            "type": "object"
+          },
+          "sha": {
+            "type": "string"
+          },
+          "tag": {
+            "type": "boolean"
+          }
+        },
+        "required": [
+          "sha",
+          "created_at",
+          "iid",
+          "tag",
+          "last?",
+          "ref",
+          "id"
+        ],
+        "type": "object"
+      },
+      "minItems": 1,
+      "type": "array"
+    }
+  },
+  "required": [
+    "deployments"
+  ],
+  "type": "object"
+}
diff --git a/spec/helpers/award_emoji_helper_spec.rb b/spec/helpers/award_emoji_helper_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..7dfd6a3f6b4d1c4e4e348355967432aa4df94fda
--- /dev/null
+++ b/spec/helpers/award_emoji_helper_spec.rb
@@ -0,0 +1,61 @@
+require 'spec_helper'
+
+describe AwardEmojiHelper do
+  describe '.toggle_award_url' do
+    context 'note on personal snippet' do
+      let(:note) { create(:note_on_personal_snippet) }
+
+      it 'returns correct url' do
+        expected_url = "/snippets/#{note.noteable.id}/notes/#{note.id}/toggle_award_emoji"
+
+        expect(helper.toggle_award_url(note)).to eq(expected_url)
+      end
+    end
+
+    context 'note on project item' do
+      let(:note) { create(:note_on_project_snippet) }
+
+      it 'returns correct url' do
+        @project = note.noteable.project
+
+        expected_url = "/#{@project.namespace.path}/#{@project.path}/notes/#{note.id}/toggle_award_emoji"
+
+        expect(helper.toggle_award_url(note)).to eq(expected_url)
+      end
+    end
+
+    context 'personal snippet' do
+      let(:snippet) { create(:personal_snippet) }
+
+      it 'returns correct url' do
+        expected_url = "/snippets/#{snippet.id}/toggle_award_emoji"
+
+        expect(helper.toggle_award_url(snippet)).to eq(expected_url)
+      end
+    end
+
+    context 'merge request' do
+      let(:merge_request) { create(:merge_request) }
+
+      it 'returns correct url' do
+        @project = merge_request.project
+
+        expected_url = "/#{@project.namespace.path}/#{@project.path}/merge_requests/#{merge_request.id}/toggle_award_emoji"
+
+        expect(helper.toggle_award_url(merge_request)).to eq(expected_url)
+      end
+    end
+
+    context 'issue' do
+      let(:issue) { create(:issue) }
+
+      it 'returns correct url' do
+        @project = issue.project
+
+        expected_url = "/#{@project.namespace.path}/#{@project.path}/issues/#{issue.id}/toggle_award_emoji"
+
+        expect(helper.toggle_award_url(issue)).to eq(expected_url)
+      end
+    end
+  end
+end
diff --git a/spec/helpers/merge_requests_helper_spec.rb b/spec/helpers/merge_requests_helper_spec.rb
index e9037749ef27724cec3ecdf330469789de4d35a7..10681af5f7e1ec9ba1a66165033d8f1023f5510a 100644
--- a/spec/helpers/merge_requests_helper_spec.rb
+++ b/spec/helpers/merge_requests_helper_spec.rb
@@ -64,7 +64,7 @@ describe MergeRequestsHelper do
 
       it do
         @project = project
-        
+
         is_expected.to eq("#1, #2, and #{other_project.namespace.path}/#{other_project.path}#3")
       end
     end
@@ -149,6 +149,50 @@ describe MergeRequestsHelper do
     end
   end
 
+  describe '#target_projects' do
+    let(:project) { create(:empty_project) }
+    let(:fork_project) { create(:empty_project, forked_from_project: project) }
+
+    context 'when target project has enabled merge requests' do
+      it 'returns the forked_from project' do
+        expect(target_projects(fork_project)).to contain_exactly(project, fork_project)
+      end
+    end
+
+    context 'when target project has disabled merge requests' do
+      it 'returns the forked project' do
+        project.project_feature.update(merge_requests_access_level: 0)
+
+        expect(target_projects(fork_project)).to contain_exactly(fork_project)
+      end
+    end
+  end
+
+  describe '#new_mr_path_from_push_event' do
+    subject(:url_params) { URI.decode_www_form(new_mr_path_from_push_event(event)).to_h }
+    let(:user) { create(:user) }
+    let(:project) { create(:empty_project, creator: user) }
+    let(:fork_project) { create(:project, forked_from_project: project, creator: user) }
+    let(:event) do
+      push_data = Gitlab::DataBuilder::Push.build_sample(fork_project, user)
+      create(:event, :pushed, project: fork_project, target: fork_project, author: user, data: push_data)
+    end
+
+    context 'when target project has enabled merge requests' do
+      it 'returns link to create merge request on source project' do
+        expect(url_params['merge_request[target_project_id]'].to_i).to eq(project.id)
+      end
+    end
+
+    context 'when target project has disabled merge requests' do
+      it 'returns link to create merge request on forked project' do
+        project.project_feature.update(merge_requests_access_level: 0)
+
+        expect(url_params['merge_request[target_project_id]'].to_i).to eq(fork_project.id)
+      end
+    end
+  end
+
   describe '#mr_issues_mentioned_but_not_closing' do
     let(:user_1) { create(:user) }
     let(:user_2) { create(:user) }
diff --git a/spec/javascripts/fixtures/environments.rb b/spec/javascripts/fixtures/environments.rb
new file mode 100644
index 0000000000000000000000000000000000000000..3474f4696ef304114440ef5aef9d716b786835e2
--- /dev/null
+++ b/spec/javascripts/fixtures/environments.rb
@@ -0,0 +1,30 @@
+require 'spec_helper'
+
+describe Projects::EnvironmentsController, '(JavaScript fixtures)', type: :controller do
+  include JavaScriptFixturesHelpers
+
+  let(:admin) { create(:admin) }
+  let(:namespace) { create(:namespace, name: 'frontend-fixtures' )}
+  let(:project) { create(:project_empty_repo, namespace: namespace, path: 'environments-project') }
+  let(:environment) { create(:environment, name: 'production', project: project) }
+
+  render_views
+
+  before(:all) do
+    clean_frontend_fixtures('environments/metrics')
+  end
+
+  before(:each) do
+    sign_in(admin)
+  end
+
+  it 'environments/metrics/metrics.html.raw' do |example|
+    get :metrics,
+      namespace_id: project.namespace,
+      project_id: project,
+      id: environment.id
+
+    expect(response).to be_success
+    store_frontend_fixture(response, example.description)
+  end
+end
diff --git a/spec/javascripts/fixtures/environments/metrics.html.haml b/spec/javascripts/fixtures/environments/metrics.html.haml
deleted file mode 100644
index e2dd95198984a7c1c9ad31a7b3ba6778f51f4227..0000000000000000000000000000000000000000
--- a/spec/javascripts/fixtures/environments/metrics.html.haml
+++ /dev/null
@@ -1,62 +0,0 @@
-.prometheus-container{ 'data-has-metrics': "false", 'data-doc-link': '/help/administration/monitoring/prometheus/index.md', 'data-prometheus-integration': '/root/hello-prometheus/services/prometheus/edit' }
-  .top-area
-    .row
-      .col-sm-6
-        %h3.page-title
-          Metrics for environment
-  .prometheus-state
-    .js-getting-started.hidden
-      .row
-        .col-md-4.col-md-offset-4.state-svg
-          %svg
-      .row
-        .col-md-6.col-md-offset-3
-          %h4.text-center.state-title
-            Get started with performance monitoring
-      .row
-        .col-md-6.col-md-offset-3
-          .description-text.text-center.state-description
-            Stay updated about the performance and health of your environment by configuring Prometheus to monitor your deployments. Learn more about performance monitoring
-      .row.state-button-section
-        .col-md-4.col-md-offset-4.text-center.state-button
-          %a.btn.btn-success
-            Configure Prometheus
-    .js-loading.hidden
-      .row
-        .col-md-4.col-md-offset-4.state-svg
-          %svg
-      .row
-        .col-md-6.col-md-offset-3
-          %h4.text-center.state-title
-            Waiting for performance data
-      .row
-        .col-md-6.col-md-offset-3
-          .description-text.text-center.state-description
-            Creating graphs uses the data from the Prometheus server. If this takes a long time, ensure that data is available.
-      .row.state-button-section
-        .col-md-4.col-md-offset-4.text-center.state-button
-          %a.btn.btn-success
-            View documentation
-    .js-unable-to-connect.hidden
-      .row
-        .col-md-4.col-md-offset-4.state-svg
-          %svg
-      .row
-        .col-md-6.col-md-offset-3
-          %h4.text-center.state-title
-            Unable to connect to Prometheus server
-      .row
-        .col-md-6.col-md-offset-3
-          .description-text.text-center.state-description
-            Ensure connectivity is available from the GitLab server to the Prometheus server
-      .row.state-button-section
-        .col-md-4.col-md-offset-4.text-center.state-button
-          %a.btn.btn-success
-            View documentation
-  .prometheus-graphs
-    .row
-      .col-sm-12
-        %svg.prometheus-graph{ 'graph-type' => 'cpu_values' }
-    .row
-      .col-sm-12
-        %svg.prometheus-graph{ 'graph-type' => 'memory_values' }
diff --git a/spec/javascripts/monitoring/deployments_spec.js b/spec/javascripts/monitoring/deployments_spec.js
new file mode 100644
index 0000000000000000000000000000000000000000..19bc11d0f2489f1b36f5bbadf29721a9a91806c9
--- /dev/null
+++ b/spec/javascripts/monitoring/deployments_spec.js
@@ -0,0 +1,133 @@
+import d3 from 'd3';
+import PrometheusGraph from '~/monitoring/prometheus_graph';
+import Deployments from '~/monitoring/deployments';
+import { prometheusMockData } from './prometheus_mock_data';
+
+describe('Metrics deployments', () => {
+  const fixtureName = 'environments/metrics/metrics.html.raw';
+  let deployment;
+  let prometheusGraph;
+
+  const graphElement = () => document.querySelector('.prometheus-graph');
+
+  preloadFixtures(fixtureName);
+
+  beforeEach((done) => {
+    // Setup the view
+    loadFixtures(fixtureName);
+
+    d3.selectAll('.prometheus-graph')
+      .append('g')
+      .attr('class', 'graph-container');
+
+    prometheusGraph = new PrometheusGraph();
+    deployment = new Deployments(1000, 500);
+
+    spyOn(prometheusGraph, 'init');
+    spyOn($, 'ajax').and.callFake(() => {
+      const d = $.Deferred();
+      d.resolve({
+        deployments: [{
+          id: 1,
+          created_at: deployment.chartData[10].time,
+          sha: 'testing',
+          tag: false,
+          ref: {
+            name: 'testing',
+          },
+        }, {
+          id: 2,
+          created_at: deployment.chartData[15].time,
+          sha: '',
+          tag: true,
+          ref: {
+            name: 'tag',
+          },
+        }],
+      });
+
+      setTimeout(done);
+
+      return d.promise();
+    });
+
+    prometheusGraph.configureGraph();
+    prometheusGraph.transformData(prometheusMockData.metrics);
+
+    deployment.init(prometheusGraph.graphSpecificProperties.memory_values.data);
+  });
+
+  it('creates line on graph for deploment', () => {
+    expect(
+      graphElement().querySelectorAll('.deployment-line').length,
+    ).toBe(2);
+  });
+
+  it('creates hidden deploy boxes', () => {
+    expect(
+      graphElement().querySelectorAll('.prometheus-graph .js-deploy-info-box').length,
+    ).toBe(2);
+  });
+
+  it('hides the info boxes by default', () => {
+    expect(
+      graphElement().querySelectorAll('.prometheus-graph .js-deploy-info-box.hidden').length,
+    ).toBe(2);
+  });
+
+  it('shows sha short code when tag is false', () => {
+    expect(
+      graphElement().querySelector('.deploy-info-1-cpu_values .js-deploy-info-box').textContent.trim(),
+    ).toContain('testin');
+  });
+
+  it('shows ref name when tag is true', () => {
+    expect(
+      graphElement().querySelector('.deploy-info-2-cpu_values .js-deploy-info-box').textContent.trim(),
+    ).toContain('tag');
+  });
+
+  it('shows info box when moving mouse over line', () => {
+    deployment.mouseOverDeployInfo(deployment.data[0].xPos, 'cpu_values');
+
+    expect(
+      graphElement().querySelectorAll('.prometheus-graph .js-deploy-info-box.hidden').length,
+    ).toBe(1);
+
+    expect(
+      graphElement().querySelector('.deploy-info-1-cpu_values .js-deploy-info-box.hidden'),
+    ).toBeNull();
+  });
+
+  it('hides previously visible info box when moving mouse away', () => {
+    deployment.mouseOverDeployInfo(500, 'cpu_values');
+
+    expect(
+      graphElement().querySelectorAll('.prometheus-graph .js-deploy-info-box.hidden').length,
+    ).toBe(2);
+
+    expect(
+      graphElement().querySelector('.deploy-info-1-cpu_values .js-deploy-info-box.hidden'),
+    ).not.toBeNull();
+  });
+
+  describe('refText', () => {
+    it('returns shortened SHA', () => {
+      expect(
+        Deployments.refText({
+          tag: false,
+          sha: '123456789',
+        }),
+      ).toBe('123456');
+    });
+
+    it('returns tag name', () => {
+      expect(
+        Deployments.refText({
+          tag: true,
+          ref: 'v1.0',
+        }),
+      ).toBe('v1.0');
+    });
+  });
+});
diff --git a/spec/javascripts/monitoring/prometheus_graph_spec.js b/spec/javascripts/monitoring/prometheus_graph_spec.js
index 4b904fc2960ab4fd1d020ba2ba50bb59c42434bd..25578bf1c6e37abf2cf232ec1273179516a79097 100644
--- a/spec/javascripts/monitoring/prometheus_graph_spec.js
+++ b/spec/javascripts/monitoring/prometheus_graph_spec.js
@@ -3,7 +3,7 @@ import PrometheusGraph from '~/monitoring/prometheus_graph';
 import { prometheusMockData } from './prometheus_mock_data';
 
 describe('PrometheusGraph', () => {
-  const fixtureName = 'static/environments/metrics.html.raw';
+  const fixtureName = 'environments/metrics/metrics.html.raw';
   const prometheusGraphContainer = '.prometheus-graph';
   const prometheusGraphContents = `${prometheusGraphContainer}[graph-type=cpu_values]`;
 
@@ -77,7 +77,7 @@ describe('PrometheusGraph', () => {
 });
 
 describe('PrometheusGraphs UX states', () => {
-  const fixtureName = 'static/environments/metrics.html.raw';
+  const fixtureName = 'environments/metrics/metrics.html.raw';
   preloadFixtures(fixtureName);
 
   beforeEach(() => {
diff --git a/spec/lib/banzai/renderer_spec.rb b/spec/lib/banzai/renderer_spec.rb
index e6f8d2a1fed62bbd0a337e07920e65a8fb9d127e..0e094405e331fe29b38e7abfb1fe4f361a0ee139 100644
--- a/spec/lib/banzai/renderer_spec.rb
+++ b/spec/lib/banzai/renderer_spec.rb
@@ -11,7 +11,7 @@ describe Banzai::Renderer do
   end
 
   describe '#render_field' do
-    let(:renderer) { Banzai::Renderer }
+    let(:renderer) { described_class }
     subject { renderer.render_field(object, :field) }
 
     context 'with a stale cache' do
diff --git a/spec/lib/constraints/group_url_constrainer_spec.rb b/spec/lib/constraints/group_url_constrainer_spec.rb
index 96dacdc5cd2ffb45b3bf388bf0ea4b744a5e3b61..f95adf3a84b88b492d3400e62d394433948fdfc5 100644
--- a/spec/lib/constraints/group_url_constrainer_spec.rb
+++ b/spec/lib/constraints/group_url_constrainer_spec.rb
@@ -17,6 +17,13 @@ describe GroupUrlConstrainer, lib: true do
       it { expect(subject.matches?(request)).to be_truthy }
     end
 
+    context 'valid request for nested group with reserved top level name' do
+      let!(:nested_group) { create(:group, path: 'api', parent: group) }
+      let!(:request) { build_request('gitlab/api') }
+
+      it { expect(subject.matches?(request)).to be_truthy }
+    end
+
     context 'invalid request' do
       let(:request) { build_request('foo') }
 
diff --git a/spec/lib/gitlab/changes_list_spec.rb b/spec/lib/gitlab/changes_list_spec.rb
index 69d86144e321fb4a73eff64f1ab2cf456a32b669..464508fcd7394faacd80df12074c26ca4f9a7095 100644
--- a/spec/lib/gitlab/changes_list_spec.rb
+++ b/spec/lib/gitlab/changes_list_spec.rb
@@ -5,7 +5,7 @@ describe Gitlab::ChangesList do
   let(:invalid_changes) { 1 }
 
   context 'when changes is a valid string' do
-    let(:changes_list) { Gitlab::ChangesList.new(valid_changes_string) }
+    let(:changes_list) { described_class.new(valid_changes_string) }
 
     it 'splits elements by newline character' do
       expect(changes_list).to contain_exactly({
diff --git a/spec/lib/gitlab/ci/build/credentials/factory_spec.rb b/spec/lib/gitlab/ci/build/credentials/factory_spec.rb
index 10b4b7a882684e1a866ac6cbab4890bbb6bd8e57..d53db05e5e6ae1f273c0ae32851abfc1f3222d72 100644
--- a/spec/lib/gitlab/ci/build/credentials/factory_spec.rb
+++ b/spec/lib/gitlab/ci/build/credentials/factory_spec.rb
@@ -3,14 +3,14 @@ require 'spec_helper'
 describe Gitlab::Ci::Build::Credentials::Factory do
   let(:build) { create(:ci_build, name: 'spinach', stage: 'test', stage_idx: 0) }
 
-  subject { Gitlab::Ci::Build::Credentials::Factory.new(build).create! }
+  subject { described_class.new(build).create! }
 
   class TestProvider
     def initialize(build); end
   end
 
   before do
-    allow_any_instance_of(Gitlab::Ci::Build::Credentials::Factory).to receive(:providers).and_return([TestProvider])
+    allow_any_instance_of(described_class).to receive(:providers).and_return([TestProvider])
   end
 
   context 'when provider is valid' do
diff --git a/spec/lib/gitlab/ci/build/credentials/registry_spec.rb b/spec/lib/gitlab/ci/build/credentials/registry_spec.rb
index 84e44dd53e2ca81d13f7b290300330ae88265760..c6054138cde1b919ab8aabab582faa46d2f8e8e3 100644
--- a/spec/lib/gitlab/ci/build/credentials/registry_spec.rb
+++ b/spec/lib/gitlab/ci/build/credentials/registry_spec.rb
@@ -4,14 +4,14 @@ describe Gitlab::Ci::Build::Credentials::Registry do
   let(:build) { create(:ci_build, name: 'spinach', stage: 'test', stage_idx: 0) }
   let(:registry_url) { 'registry.example.com:5005' }
 
-  subject { Gitlab::Ci::Build::Credentials::Registry.new(build) }
+  subject { described_class.new(build) }
 
   before do
     stub_container_registry_config(host_port: registry_url)
   end
 
   it 'contains valid DockerRegistry credentials' do
-    expect(subject).to be_kind_of(Gitlab::Ci::Build::Credentials::Registry)
+    expect(subject).to be_kind_of(described_class)
 
     expect(subject.username).to eq 'gitlab-ci-token'
     expect(subject.password).to eq build.token
@@ -20,7 +20,7 @@ describe Gitlab::Ci::Build::Credentials::Registry do
   end
 
   describe '.valid?' do
-    subject { Gitlab::Ci::Build::Credentials::Registry.new(build).valid? }
+    subject { described_class.new(build).valid? }
 
     context 'when registry is enabled' do
       before do
diff --git a/spec/lib/gitlab/current_settings_spec.rb b/spec/lib/gitlab/current_settings_spec.rb
index b01c4805a3449a624875fafad045490b645a044f..c796c98ec9f08e60018eec1da2419dbe48a0214d 100644
--- a/spec/lib/gitlab/current_settings_spec.rb
+++ b/spec/lib/gitlab/current_settings_spec.rb
@@ -10,7 +10,7 @@ describe Gitlab::CurrentSettings do
   describe '#current_application_settings' do
     context 'with DB available' do
       before do
-        allow_any_instance_of(Gitlab::CurrentSettings).to receive(:connect_to_db?).and_return(true)
+        allow_any_instance_of(described_class).to receive(:connect_to_db?).and_return(true)
       end
 
       it 'attempts to use cached values first' do
@@ -36,7 +36,7 @@ describe Gitlab::CurrentSettings do
 
     context 'with DB unavailable' do
       before do
-        allow_any_instance_of(Gitlab::CurrentSettings).to receive(:connect_to_db?).and_return(false)
+        allow_any_instance_of(described_class).to receive(:connect_to_db?).and_return(false)
       end
 
       it 'returns an in-memory ApplicationSetting object' do
diff --git a/spec/lib/gitlab/cycle_analytics/base_event_fetcher_spec.rb b/spec/lib/gitlab/cycle_analytics/base_event_fetcher_spec.rb
index c455cd9b94259efe73b1fef98e72ad4176a52f01..d8757c601ab854864c165909ec88ff7a51209807 100644
--- a/spec/lib/gitlab/cycle_analytics/base_event_fetcher_spec.rb
+++ b/spec/lib/gitlab/cycle_analytics/base_event_fetcher_spec.rb
@@ -20,7 +20,7 @@ describe Gitlab::CycleAnalytics::BaseEventFetcher do
 
   before do
     allow_any_instance_of(Gitlab::ReferenceExtractor).to receive(:issues).and_return(Issue.all)
-    allow_any_instance_of(Gitlab::CycleAnalytics::BaseEventFetcher).to receive(:serialize) do |event|
+    allow_any_instance_of(described_class).to receive(:serialize) do |event|
       event
     end
 
diff --git a/spec/lib/gitlab/database/migration_helpers_spec.rb b/spec/lib/gitlab/database/migration_helpers_spec.rb
index a044b871730cdb4052e4566f5ad2c0e5fd50fff8..737fac14f920872a1b0d2c409d842eeb89e639e7 100644
--- a/spec/lib/gitlab/database/migration_helpers_spec.rb
+++ b/spec/lib/gitlab/database/migration_helpers_spec.rb
@@ -726,4 +726,37 @@ describe Gitlab::Database::MigrationHelpers, lib: true do
       expect(model.column_for(:users, :kittens)).to be_nil
     end
   end
+
+  describe '#replace_sql' do
+    context 'using postgres' do
+      before do
+        allow(Gitlab::Database).to receive(:mysql?).and_return(false)
+      end
+
+      it 'builds the sql with correct functions' do
+        expect(model.replace_sql(Arel::Table.new(:users)[:first_name], "Alice", "Eve").to_s).
+          to include('regexp_replace')
+      end
+    end
+
+    context 'using mysql' do
+      before do
+        allow(Gitlab::Database).to receive(:mysql?).and_return(true)
+      end
+
+      it 'builds the sql with the correct functions' do
+        expect(model.replace_sql(Arel::Table.new(:users)[:first_name], "Alice", "Eve").to_s).
+          to include('locate', 'insert')
+      end
+    end
+
+    describe 'results' do
+      let!(:user) { create(:user, name: 'Kathy Alice Aliceson') }
+
+      it 'replaces the correct part of the string' do
+        model.update_column_in_batches(:users, :name, model.replace_sql(Arel::Table.new(:users)[:name], 'Alice', 'Eve'))
+        expect(user.reload.name).to eq('Kathy Eve Aliceson')
+      end
+    end
+  end
 end
diff --git a/spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_base_spec.rb b/spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_base_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..64bc5fc042960fdc004c86d51b5ff6da1b2321be
--- /dev/null
+++ b/spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_base_spec.rb
@@ -0,0 +1,197 @@
+require 'spec_helper'
+
+describe Gitlab::Database::RenameReservedPathsMigration::V1::RenameBase do
+  let(:migration) { FakeRenameReservedPathMigrationV1.new }
+  let(:subject) { described_class.new(['the-path'], migration) }
+
+  before do
+    allow(migration).to receive(:say)
+  end
+
+  def migration_namespace(namespace)
+    Gitlab::Database::RenameReservedPathsMigration::V1::MigrationClasses::
+      Namespace.find(namespace.id)
+  end
+
+  def migration_project(project)
+    Gitlab::Database::RenameReservedPathsMigration::V1::MigrationClasses::
+      Project.find(project.id)
+  end
+
+  describe "#remove_last_ocurrence" do
+    it "removes only the last occurance of a string" do
+      input = "this/is/a-word-to-replace/namespace/with/a-word-to-replace"
+
+      expect(subject.remove_last_occurrence(input, "a-word-to-replace"))
+        .to eq("this/is/a-word-to-replace/namespace/with/")
+    end
+  end
+
+  describe '#remove_cached_html_for_projects' do
+    let(:project) { create(:empty_project, description_html: 'Project description') }
+
+    it 'removes description_html from projects' do
+      subject.remove_cached_html_for_projects([project.id])
+
+      expect(project.reload.description_html).to be_nil
+    end
+
+    it 'removes issue descriptions' do
+      issue = create(:issue, project: project, description_html: 'Issue description')
+
+      subject.remove_cached_html_for_projects([project.id])
+
+      expect(issue.reload.description_html).to be_nil
+    end
+
+    it 'removes merge request descriptions' do
+      merge_request = create(:merge_request,
+                             source_project: project,
+                             target_project: project,
+                             description_html: 'MergeRequest description')
+
+      subject.remove_cached_html_for_projects([project.id])
+
+      expect(merge_request.reload.description_html).to be_nil
+    end
+
+    it 'removes note html' do
+      note = create(:note,
+                    project: project,
+                    noteable: create(:issue, project: project),
+                    note_html: 'note description')
+
+      subject.remove_cached_html_for_projects([project.id])
+
+      expect(note.reload.note_html).to be_nil
+    end
+
+    it 'removes milestone description' do
+      milestone = create(:milestone,
+                    project: project,
+                    description_html: 'milestone description')
+
+      subject.remove_cached_html_for_projects([project.id])
+
+      expect(milestone.reload.description_html).to be_nil
+    end
+  end
+
+  describe '#rename_path_for_routable' do
+    context 'for namespaces' do
+      let(:namespace) { create(:namespace, path: 'the-path') }
+      it "renames namespaces called the-path" do
+        subject.rename_path_for_routable(migration_namespace(namespace))
+
+        expect(namespace.reload.path).to eq("the-path0")
+      end
+
+      it "renames the route to the namespace" do
+        subject.rename_path_for_routable(migration_namespace(namespace))
+
+        expect(Namespace.find(namespace.id).full_path).to eq("the-path0")
+      end
+
+      it "renames the route for projects of the namespace" do
+        project = create(:project, path: "project-path", namespace: namespace)
+
+        subject.rename_path_for_routable(migration_namespace(namespace))
+
+        expect(project.route.reload.path).to eq("the-path0/project-path")
+      end
+
+      it 'returns the old & the new path' do
+        old_path, new_path = subject.rename_path_for_routable(migration_namespace(namespace))
+
+        expect(old_path).to eq('the-path')
+        expect(new_path).to eq('the-path0')
+      end
+
+      context "the-path namespace -> subgroup -> the-path0 project" do
+        it "updates the route of the project correctly" do
+          subgroup = create(:group, path: "subgroup", parent: namespace)
+          project = create(:project, path: "the-path0", namespace: subgroup)
+
+          subject.rename_path_for_routable(migration_namespace(namespace))
+
+          expect(project.route.reload.path).to eq("the-path0/subgroup/the-path0")
+        end
+      end
+    end
+
+    context 'for projects' do
+      let(:parent) { create(:namespace, path: 'the-parent') }
+      let(:project) { create(:empty_project, path: 'the-path', namespace: parent) }
+
+      it 'renames the project called `the-path`' do
+        subject.rename_path_for_routable(migration_project(project))
+
+        expect(project.reload.path).to eq('the-path0')
+      end
+
+      it 'renames the route for the project' do
+        subject.rename_path_for_routable(project)
+
+        expect(project.reload.route.path).to eq('the-parent/the-path0')
+      end
+
+      it 'returns the old & new path' do
+        old_path, new_path = subject.rename_path_for_routable(migration_project(project))
+
+        expect(old_path).to eq('the-parent/the-path')
+        expect(new_path).to eq('the-parent/the-path0')
+      end
+    end
+  end
+
+  describe '#move_pages' do
+    it 'moves the pages directory' do
+      expect(subject).to receive(:move_folders)
+                           .with(TestEnv.pages_path, 'old-path', 'new-path')
+
+      subject.move_pages('old-path', 'new-path')
+    end
+  end
+
+  describe "#move_uploads" do
+    let(:test_dir) { File.join(Rails.root, 'tmp', 'tests', 'rename_reserved_paths') }
+    let(:uploads_dir) { File.join(test_dir, 'public', 'uploads') }
+
+    it 'moves subdirectories in the uploads folder' do
+      expect(subject).to receive(:uploads_dir).and_return(uploads_dir)
+      expect(subject).to receive(:move_folders).with(uploads_dir, 'old_path', 'new_path')
+
+      subject.move_uploads('old_path', 'new_path')
+    end
+
+    it "doesn't move uploads when they are stored in object storage" do
+      expect(subject).to receive(:file_storage?).and_return(false)
+      expect(subject).not_to receive(:move_folders)
+
+      subject.move_uploads('old_path', 'new_path')
+    end
+  end
+
+  describe '#move_folders' do
+    let(:test_dir) { File.join(Rails.root, 'tmp', 'tests', 'rename_reserved_paths') }
+    let(:uploads_dir) { File.join(test_dir, 'public', 'uploads') }
+
+    before do
+      FileUtils.remove_dir(test_dir) if File.directory?(test_dir)
+      FileUtils.mkdir_p(uploads_dir)
+      allow(subject).to receive(:uploads_dir).and_return(uploads_dir)
+    end
+
+    it 'moves a folder with files' do
+      source = File.join(uploads_dir, 'parent-group', 'sub-group')
+      FileUtils.mkdir_p(source)
+      destination = File.join(uploads_dir, 'parent-group', 'moved-group')
+      FileUtils.touch(File.join(source, 'test.txt'))
+      expected_file = File.join(destination, 'test.txt')
+
+      subject.move_folders(uploads_dir, File.join('parent-group', 'sub-group'), File.join('parent-group', 'moved-group'))
+
+      expect(File.exist?(expected_file)).to be(true)
+    end
+  end
+end
diff --git a/spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_namespaces_spec.rb b/spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_namespaces_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..a25c5da488ad576423124c7e36165c12840f1420
--- /dev/null
+++ b/spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_namespaces_spec.rb
@@ -0,0 +1,171 @@
+require 'spec_helper'
+
+describe Gitlab::Database::RenameReservedPathsMigration::V1::RenameNamespaces do
+  let(:migration) { FakeRenameReservedPathMigrationV1.new }
+  let(:subject) { described_class.new(['the-path'], migration) }
+
+  before do
+    allow(migration).to receive(:say)
+  end
+
+  def migration_namespace(namespace)
+    Gitlab::Database::RenameReservedPathsMigration::V1::MigrationClasses::
+      Namespace.find(namespace.id)
+  end
+
+  describe '#namespaces_for_paths' do
+    context 'nested namespaces' do
+      let(:subject) { described_class.new(['parent/the-Path'], migration) }
+
+      it 'includes the namespace' do
+        parent = create(:namespace, path: 'parent')
+        child = create(:namespace, path: 'the-path', parent: parent)
+
+        found_ids = subject.namespaces_for_paths(type: :child).
+                      map(&:id)
+        expect(found_ids).to contain_exactly(child.id)
+      end
+    end
+
+    context 'for child namespaces' do
+      it 'only returns child namespaces with the correct path' do
+        _root_namespace = create(:namespace, path: 'THE-path')
+        _other_path = create(:namespace,
+                             path: 'other',
+                             parent: create(:namespace))
+        namespace = create(:namespace,
+                           path: 'the-path',
+                           parent: create(:namespace))
+
+        found_ids = subject.namespaces_for_paths(type: :child).
+                      map(&:id)
+        expect(found_ids).to contain_exactly(namespace.id)
+      end
+    end
+
+    context 'for top levelnamespaces' do
+      it 'only returns child namespaces with the correct path' do
+        root_namespace = create(:namespace, path: 'the-path')
+        _other_path = create(:namespace, path: 'other')
+        _child_namespace = create(:namespace,
+                                  path: 'the-path',
+                                  parent: create(:namespace))
+
+        found_ids = subject.namespaces_for_paths(type: :top_level).
+                      map(&:id)
+        expect(found_ids).to contain_exactly(root_namespace.id)
+      end
+    end
+  end
+
+  describe '#move_repositories' do
+    let(:namespace) { create(:group, name: 'hello-group') }
+    it 'moves a project for a namespace' do
+      create(:project, namespace: namespace, path: 'hello-project')
+      expected_path = File.join(TestEnv.repos_path, 'bye-group', 'hello-project.git')
+
+      subject.move_repositories(namespace, 'hello-group', 'bye-group')
+
+      expect(File.directory?(expected_path)).to be(true)
+    end
+
+    it 'moves a namespace in a subdirectory correctly' do
+      child_namespace = create(:group, name: 'sub-group', parent: namespace)
+      create(:project, namespace: child_namespace, path: 'hello-project')
+
+      expected_path = File.join(TestEnv.repos_path, 'hello-group', 'renamed-sub-group', 'hello-project.git')
+
+      subject.move_repositories(child_namespace, 'hello-group/sub-group', 'hello-group/renamed-sub-group')
+
+      expect(File.directory?(expected_path)).to be(true)
+    end
+
+    it 'moves a parent namespace with subdirectories' do
+      child_namespace = create(:group, name: 'sub-group', parent: namespace)
+      create(:project, namespace: child_namespace, path: 'hello-project')
+      expected_path = File.join(TestEnv.repos_path, 'renamed-group', 'sub-group', 'hello-project.git')
+
+      subject.move_repositories(child_namespace, 'hello-group', 'renamed-group')
+
+      expect(File.directory?(expected_path)).to be(true)
+    end
+  end
+
+  describe "#child_ids_for_parent" do
+    it "collects child ids for all levels" do
+      parent = create(:namespace)
+      first_child = create(:namespace, parent: parent)
+      second_child = create(:namespace, parent: parent)
+      third_child = create(:namespace, parent: second_child)
+      all_ids = [parent.id, first_child.id, second_child.id, third_child.id]
+
+      collected_ids = subject.child_ids_for_parent(parent, ids: [parent.id])
+
+      expect(collected_ids).to contain_exactly(*all_ids)
+    end
+  end
+
+  describe "#rename_namespace" do
+    let(:namespace) { create(:namespace, path: 'the-path') }
+
+    it 'renames paths & routes for the namespace' do
+      expect(subject).to receive(:rename_path_for_routable).
+                           with(namespace).
+                           and_call_original
+
+      subject.rename_namespace(namespace)
+
+      expect(namespace.reload.path).to eq('the-path0')
+    end
+
+    it "moves the the repository for a project in the namespace" do
+      create(:project, namespace: namespace, path: "the-path-project")
+      expected_repo = File.join(TestEnv.repos_path, "the-path0", "the-path-project.git")
+
+      subject.rename_namespace(namespace)
+
+      expect(File.directory?(expected_repo)).to be(true)
+    end
+
+    it "moves the uploads for the namespace" do
+      expect(subject).to receive(:move_uploads).with("the-path", "the-path0")
+
+      subject.rename_namespace(namespace)
+    end
+
+    it "moves the pages for the namespace" do
+      expect(subject).to receive(:move_pages).with("the-path", "the-path0")
+
+      subject.rename_namespace(namespace)
+    end
+
+    it 'invalidates the markdown cache of related projects' do
+      project = create(:empty_project, namespace: namespace, path: "the-path-project")
+
+      expect(subject).to receive(:remove_cached_html_for_projects).with([project.id])
+
+      subject.rename_namespace(namespace)
+    end
+  end
+
+  describe '#rename_namespaces' do
+    let!(:top_level_namespace) { create(:namespace, path: 'the-path') }
+    let!(:child_namespace) do
+      create(:namespace, path: 'the-path', parent: create(:namespace))
+    end
+
+    it 'renames top level namespaces the namespace' do
+      expect(subject).to receive(:rename_namespace).
+                           with(migration_namespace(top_level_namespace))
+
+      subject.rename_namespaces(type: :top_level)
+    end
+
+    it 'renames child namespaces' do
+      expect(subject).to receive(:rename_namespace).
+                           with(migration_namespace(child_namespace))
+
+      subject.rename_namespaces(type: :child)
+    end
+  end
+end
diff --git a/spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_projects_spec.rb b/spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_projects_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..59e8de2712d05b0a7a3418501985a3460081b5b5
--- /dev/null
+++ b/spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_projects_spec.rb
@@ -0,0 +1,102 @@
+require 'spec_helper'
+
+describe Gitlab::Database::RenameReservedPathsMigration::V1::RenameProjects do
+  let(:migration) { FakeRenameReservedPathMigrationV1.new }
+  let(:subject) { described_class.new(['the-path'], migration) }
+
+  before do
+    allow(migration).to receive(:say)
+  end
+
+  describe '#projects_for_paths' do
+    it 'searches using nested paths' do
+      namespace = create(:namespace, path: 'hello')
+      project = create(:empty_project, path: 'THE-path', namespace: namespace)
+
+      result_ids = described_class.new(['Hello/the-path'], migration).
+                     projects_for_paths.map(&:id)
+
+      expect(result_ids).to contain_exactly(project.id)
+    end
+
+    it 'includes the correct projects' do
+      project = create(:empty_project, path: 'THE-path')
+      _other_project = create(:empty_project)
+
+      result_ids = subject.projects_for_paths.map(&:id)
+
+      expect(result_ids).to contain_exactly(project.id)
+    end
+  end
+
+  describe '#rename_projects' do
+    let!(:projects) { create_list(:empty_project, 2, path: 'the-path') }
+
+    it 'renames each project' do
+      expect(subject).to receive(:rename_project).twice
+
+      subject.rename_projects
+    end
+
+    it 'invalidates the markdown cache of related projects' do
+      expect(subject).to receive(:remove_cached_html_for_projects).
+                           with(projects.map(&:id))
+
+      subject.rename_projects
+    end
+  end
+
+  describe '#rename_project' do
+    let(:project) do
+      create(:empty_project,
+             path: 'the-path',
+             namespace: create(:namespace, path: 'known-parent' ))
+    end
+
+    it 'renames path & route for the project' do
+      expect(subject).to receive(:rename_path_for_routable).
+                           with(project).
+                           and_call_original
+
+      subject.rename_project(project)
+
+      expect(project.reload.path).to eq('the-path0')
+    end
+
+    it 'moves the wiki & the repo' do
+      expect(subject).to receive(:move_repository).
+                           with(project, 'known-parent/the-path.wiki', 'known-parent/the-path0.wiki')
+      expect(subject).to receive(:move_repository).
+                           with(project, 'known-parent/the-path', 'known-parent/the-path0')
+
+      subject.rename_project(project)
+    end
+
+    it 'moves uploads' do
+      expect(subject).to receive(:move_uploads).
+                           with('known-parent/the-path', 'known-parent/the-path0')
+
+      subject.rename_project(project)
+    end
+
+    it 'moves pages' do
+      expect(subject).to receive(:move_pages).
+                           with('known-parent/the-path', 'known-parent/the-path0')
+
+      subject.rename_project(project)
+    end
+  end
+
+  describe '#move_repository' do
+    let(:known_parent) { create(:namespace, path: 'known-parent') }
+    let(:project) { create(:project, path: 'the-path', namespace: known_parent) }
+
+    it 'moves the repository for a project' do
+      expected_path = File.join(TestEnv.repos_path, 'known-parent', 'new-repo.git')
+
+      subject.move_repository(project, 'known-parent/the-path', 'known-parent/new-repo')
+
+      expect(File.directory?(expected_path)).to be(true)
+    end
+  end
+end
diff --git a/spec/lib/gitlab/database/rename_reserved_paths_migration/v1_spec.rb b/spec/lib/gitlab/database/rename_reserved_paths_migration/v1_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..f8cc1eb91ec9945097e9f1e77ba713ea99a8c495
--- /dev/null
+++ b/spec/lib/gitlab/database/rename_reserved_paths_migration/v1_spec.rb
@@ -0,0 +1,54 @@
+require 'spec_helper'
+
+shared_examples 'renames child namespaces' do |type|
+  it 'renames namespaces' do
+    rename_namespaces = double
+    expect(described_class::RenameNamespaces).
+      to receive(:new).with(['first-path', 'second-path'], subject).
+           and_return(rename_namespaces)
+    expect(rename_namespaces).to receive(:rename_namespaces).
+                                   with(type: :child)
+
+    subject.rename_wildcard_paths(['first-path', 'second-path'])
+  end
+end
+
+describe Gitlab::Database::RenameReservedPathsMigration::V1 do
+  let(:subject) { FakeRenameReservedPathMigrationV1.new }
+
+  before do
+    allow(subject).to receive(:say)
+  end
+
+  describe '#rename_child_paths' do
+    it_behaves_like 'renames child namespaces'
+  end
+
+  describe '#rename_wildcard_paths' do
+    it_behaves_like 'renames child namespaces'
+
+    it 'should rename projects' do
+      rename_projects = double
+      expect(described_class::RenameProjects).
+        to receive(:new).with(['the-path'], subject).
+             and_return(rename_projects)
+
+      expect(rename_projects).to receive(:rename_projects)
+
+      subject.rename_wildcard_paths(['the-path'])
+    end
+  end
+
+  describe '#rename_root_paths' do
+    it 'should rename namespaces' do
+      rename_namespaces = double
+      expect(described_class::RenameNamespaces).
+        to receive(:new).with(['the-path'], subject).
+             and_return(rename_namespaces)
+      expect(rename_namespaces).to receive(:rename_namespaces).
+                           with(type: :top_level)
+
+      subject.rename_root_paths('the-path')
+    end
+  end
+end
diff --git a/spec/lib/gitlab/git/repository_spec.rb b/spec/lib/gitlab/git/repository_spec.rb
index f88653cb1fe5e3e013adbe6e85adda3a45c048a5..ddedb7c34438b98712496305ed16fed729d26843 100644
--- a/spec/lib/gitlab/git/repository_spec.rb
+++ b/spec/lib/gitlab/git/repository_spec.rb
@@ -24,21 +24,26 @@ describe Gitlab::Git::Repository, seed_helper: true do
       end
     end
 
-    # TODO: Uncomment when feature is reenabled
-    # context 'with gitaly enabled' do
-    #   before { stub_gitaly }
-    #
-    #   it 'gets the branch name from GitalyClient' do
-    #     expect_any_instance_of(Gitlab::GitalyClient::Ref).to receive(:default_branch_name)
-    #     repository.root_ref
-    #   end
-    #
-    #   it 'wraps GRPC exceptions' do
-    #     expect_any_instance_of(Gitlab::GitalyClient::Ref).to receive(:default_branch_name).
-    #       and_raise(GRPC::Unknown)
-    #     expect { repository.root_ref }.to raise_error(Gitlab::Git::CommandError)
-    #   end
-    # end
+    context 'with gitaly enabled' do
+      before { stub_gitaly }
+
+      it 'gets the branch name from GitalyClient' do
+        expect_any_instance_of(Gitlab::GitalyClient::Ref).to receive(:default_branch_name)
+        repository.root_ref
+      end
+
+      it 'wraps GRPC not found' do
+        expect_any_instance_of(Gitlab::GitalyClient::Ref).to receive(:default_branch_name).
+          and_raise(GRPC::NotFound)
+        expect { repository.root_ref }.to raise_error(Gitlab::Git::Repository::NoRepository)
+      end
+
+      it 'wraps GRPC exceptions' do
+        expect_any_instance_of(Gitlab::GitalyClient::Ref).to receive(:default_branch_name).
+          and_raise(GRPC::Unknown)
+        expect { repository.root_ref }.to raise_error(Gitlab::Git::CommandError)
+      end
+    end
   end
 
   describe "#rugged" do
@@ -113,21 +118,26 @@ describe Gitlab::Git::Repository, seed_helper: true do
     it { is_expected.to include("master") }
     it { is_expected.not_to include("branch-from-space") }
 
-    # TODO: Uncomment when feature is reenabled
-    # context 'with gitaly enabled' do
-    #   before { stub_gitaly }
-    #
-    #   it 'gets the branch names from GitalyClient' do
-    #     expect_any_instance_of(Gitlab::GitalyClient::Ref).to receive(:branch_names)
-    #     subject
-    #   end
-    #
-    #   it 'wraps GRPC exceptions' do
-    #     expect_any_instance_of(Gitlab::GitalyClient::Ref).to receive(:branch_names).
-    #       and_raise(GRPC::Unknown)
-    #     expect { subject }.to raise_error(Gitlab::Git::CommandError)
-    #   end
-    # end
+    context 'with gitaly enabled' do
+      before { stub_gitaly }
+
+      it 'gets the branch names from GitalyClient' do
+        expect_any_instance_of(Gitlab::GitalyClient::Ref).to receive(:branch_names)
+        subject
+      end
+
+      it 'wraps GRPC not found' do
+        expect_any_instance_of(Gitlab::GitalyClient::Ref).to receive(:branch_names).
+          and_raise(GRPC::NotFound)
+        expect { subject }.to raise_error(Gitlab::Git::Repository::NoRepository)
+      end
+
+      it 'wraps GRPC other exceptions' do
+        expect_any_instance_of(Gitlab::GitalyClient::Ref).to receive(:branch_names).
+          and_raise(GRPC::Unknown)
+        expect { subject }.to raise_error(Gitlab::Git::CommandError)
+      end
+    end
   end
 
   describe '#tag_names' do
@@ -145,21 +155,26 @@ describe Gitlab::Git::Repository, seed_helper: true do
     it { is_expected.to include("v1.0.0") }
     it { is_expected.not_to include("v5.0.0") }
 
-    # TODO: Uncomment when feature is reenabled
-    # context 'with gitaly enabled' do
-    #   before { stub_gitaly }
-    #
-    #   it 'gets the tag names from GitalyClient' do
-    #     expect_any_instance_of(Gitlab::GitalyClient::Ref).to receive(:tag_names)
-    #     subject
-    #   end
-    #
-    #   it 'wraps GRPC exceptions' do
-    #     expect_any_instance_of(Gitlab::GitalyClient::Ref).to receive(:tag_names).
-    #       and_raise(GRPC::Unknown)
-    #     expect { subject }.to raise_error(Gitlab::Git::CommandError)
-    #   end
-    # end
+    context 'with gitaly enabled' do
+      before { stub_gitaly }
+
+      it 'gets the tag names from GitalyClient' do
+        expect_any_instance_of(Gitlab::GitalyClient::Ref).to receive(:tag_names)
+        subject
+      end
+
+      it 'wraps GRPC not found' do
+        expect_any_instance_of(Gitlab::GitalyClient::Ref).to receive(:tag_names).
+          and_raise(GRPC::NotFound)
+        expect { subject }.to raise_error(Gitlab::Git::Repository::NoRepository)
+      end
+
+      it 'wraps GRPC exceptions' do
+        expect_any_instance_of(Gitlab::GitalyClient::Ref).to receive(:tag_names).
+          and_raise(GRPC::Unknown)
+        expect { subject }.to raise_error(Gitlab::Git::CommandError)
+      end
+    end
   end
 
   shared_examples 'archive check' do |extenstion|
@@ -1074,20 +1089,8 @@ describe Gitlab::Git::Repository, seed_helper: true do
   end
 
   describe '#branch_count' do
-    before(:each) do
-      valid_ref   = double(:ref)
-      invalid_ref = double(:ref)
-
-      allow(valid_ref).to receive_messages(name: 'master', target: double(:target))
-
-      allow(invalid_ref).to receive_messages(name: 'bad-branch')
-      allow(invalid_ref).to receive(:target) { raise Rugged::ReferenceError }
-
-      allow(repository.rugged).to receive_messages(branches: [valid_ref, invalid_ref])
-    end
-
     it 'returns the number of branches' do
-      expect(repository.branch_count).to eq(1)
+      expect(repository.branch_count).to eq(9)
     end
   end
 
diff --git a/spec/lib/gitlab/git/util_spec.rb b/spec/lib/gitlab/git/util_spec.rb
index bcca4d4c746f5d1fdad1e635ea6654afcb576898..69d3ca5539724a61d42ce694ae0f7d3416cfcf53 100644
--- a/spec/lib/gitlab/git/util_spec.rb
+++ b/spec/lib/gitlab/git/util_spec.rb
@@ -9,7 +9,7 @@ describe Gitlab::Git::Util do
       ["foo\n\n", 2],
     ].each do |string, line_count|
       it "counts #{line_count} lines in #{string.inspect}" do
-        expect(Gitlab::Git::Util.count_lines(string)).to eq(line_count)
+        expect(described_class.count_lines(string)).to eq(line_count)
       end
     end
   end
diff --git a/spec/lib/gitlab/gitaly_client/ref_spec.rb b/spec/lib/gitlab/gitaly_client/ref_spec.rb
index 5405eafd281c131b24918fb6e858ce0abbafb9e7..255f23e6270ea2e57a27d68506b2ba41a6e6cd3c 100644
--- a/spec/lib/gitlab/gitaly_client/ref_spec.rb
+++ b/spec/lib/gitlab/gitaly_client/ref_spec.rb
@@ -3,7 +3,7 @@ require 'spec_helper'
 describe Gitlab::GitalyClient::Ref do
   let(:project) { create(:empty_project) }
   let(:repo_path) { project.repository.path_to_repo }
-  let(:client) { Gitlab::GitalyClient::Ref.new(project.repository) }
+  let(:client) { described_class.new(project.repository) }
 
   before do
     allow(Gitlab.config.gitaly).to receive(:enabled).and_return(true)
diff --git a/spec/lib/gitlab/import_export/fork_spec.rb b/spec/lib/gitlab/import_export/fork_spec.rb
index c5ce06afd73143dfb1f89ad4139a0189aacd2a10..42f3fc59f041ddee07c60c1793820258c22bf501 100644
--- a/spec/lib/gitlab/import_export/fork_spec.rb
+++ b/spec/lib/gitlab/import_export/fork_spec.rb
@@ -3,7 +3,7 @@ require 'spec_helper'
 describe 'forked project import', services: true do
   let(:user) { create(:user) }
   let!(:project_with_repo) { create(:project, :test_repo, name: 'test-repo-restorer', path: 'test-repo-restorer') }
-  let!(:project) { create(:empty_project) }
+  let!(:project) { create(:empty_project, name: 'test-repo-restorer-no-repo', path: 'test-repo-restorer-no-repo') }
   let(:export_path) { "#{Dir.tmpdir}/project_tree_saver_spec" }
   let(:shared) { Gitlab::ImportExport::Shared.new(relative_path: project.path_with_namespace) }
   let(:forked_from_project) { create(:project) }
diff --git a/spec/lib/gitlab/import_export/project.json b/spec/lib/gitlab/import_export/project.json
index bfecfa28ed13d33e44112dff7ffefc65be1954c8..fdbb6a0556dae2cf587b71c556793062c0031050 100644
--- a/spec/lib/gitlab/import_export/project.json
+++ b/spec/lib/gitlab/import_export/project.json
@@ -2,6 +2,7 @@
   "description": "Nisi et repellendus ut enim quo accusamus vel magnam.",
   "visibility_level": 10,
   "archived": false,
+  "description_html": "description",
   "labels": [
     {
       "id": 2,
diff --git a/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb b/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb
index 0e9607c5bd3985e5408a909e03a48b93ef5c3bc8..14338515892f3af832d690a20f8f79faca4614bd 100644
--- a/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb
+++ b/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb
@@ -30,6 +30,10 @@ describe Gitlab::ImportExport::ProjectTreeRestorer, services: true do
         expect(project.project_feature.merge_requests_access_level).to eq(ProjectFeature::ENABLED)
       end
 
+      it 'has the project html description' do
+        expect(Project.find_by_path('project').description_html).to eq('description')
+      end
+
       it 'has the same label associated to two issues' do
         expect(ProjectLabel.find_by_title('test2').issues.count).to eq(2)
       end
diff --git a/spec/lib/gitlab/import_export/project_tree_saver_spec.rb b/spec/lib/gitlab/import_export/project_tree_saver_spec.rb
index d2d89e3b0195a18d52f89c485ef8538aaee8c871..6e145947104be3121539b2a5898beadd7f77fc60 100644
--- a/spec/lib/gitlab/import_export/project_tree_saver_spec.rb
+++ b/spec/lib/gitlab/import_export/project_tree_saver_spec.rb
@@ -189,6 +189,16 @@ describe Gitlab::ImportExport::ProjectTreeSaver, services: true do
           end
         end
       end
+
+      context 'project attributes' do
+        it 'contains the html description' do
+          expect(saved_project_json).to include("description_html" => 'description')
+        end
+
+        it 'does not contain the runners token' do
+          expect(saved_project_json).not_to include("runners_token" => 'token')
+        end
+      end
     end
   end
 
@@ -209,6 +219,7 @@ describe Gitlab::ImportExport::ProjectTreeSaver, services: true do
                      releases: [release],
                      group: group
                     )
+    project.update(description_html: 'description')
     project_label = create(:label, project: project)
     group_label = create(:group_label, group: group)
     create(:label_link, label: project_label, target: issue)
diff --git a/spec/lib/gitlab/import_export/reader_spec.rb b/spec/lib/gitlab/import_export/reader_spec.rb
index 48d74b07e27066008cff424623c57358178ccd7f..d700af142be996a268effd12e6681aadfc6b93bd 100644
--- a/spec/lib/gitlab/import_export/reader_spec.rb
+++ b/spec/lib/gitlab/import_export/reader_spec.rb
@@ -5,7 +5,7 @@ describe Gitlab::ImportExport::Reader, lib: true  do
   let(:test_config) { 'spec/support/import_export/import_export.yml' }
   let(:project_tree_hash) do
     {
-      only: [:name, :path],
+      except: [:id, :created_at],
       include: [:issues, :labels,
                 { merge_requests: {
                   only: [:id],
diff --git a/spec/lib/gitlab/import_export/safe_model_attributes.yml b/spec/lib/gitlab/import_export/safe_model_attributes.yml
index 0372e3f7dbf2e8d02f949e8a1421222bbf3b9926..ebfaab4eacd78fe5df434366aedd250a9c0418eb 100644
--- a/spec/lib/gitlab/import_export/safe_model_attributes.yml
+++ b/spec/lib/gitlab/import_export/safe_model_attributes.yml
@@ -329,6 +329,28 @@ Project:
 - snippets_enabled
 - visibility_level
 - archived
+- created_at
+- updated_at
+- last_activity_at
+- star_count
+- ci_id
+- shared_runners_enabled
+- build_coverage_regex
+- build_allow_git_fetchs
+- build_timeout
+- pending_delete
+- public_builds
+- last_repository_check_failed
+- last_repository_check_at
+- container_registry_enabled
+- only_allow_merge_if_pipeline_succeeds
+- has_external_issue_tracker
+- request_access_enabled
+- has_external_wiki
+- only_allow_merge_if_all_discussions_are_resolved
+- auto_cancel_pending_pipelines
+- printing_merge_request_link_enabled
+- build_allow_git_fetch
 Author:
 - name
 ProjectFeature:
diff --git a/spec/lib/gitlab/ldap/person_spec.rb b/spec/lib/gitlab/ldap/person_spec.rb
index 9a556cde5d56bc9872eb7f0637c38ce6735a97cc..087c4d8c92c5ee733618f476dd472dd68315668c 100644
--- a/spec/lib/gitlab/ldap/person_spec.rb
+++ b/spec/lib/gitlab/ldap/person_spec.rb
@@ -20,7 +20,7 @@ describe Gitlab::LDAP::Person do
     it 'uses the configured name attribute and handles values as an array' do
       name = 'John Doe'
       entry['cn'] = [name]
-      person = Gitlab::LDAP::Person.new(entry, 'ldapmain')
+      person = described_class.new(entry, 'ldapmain')
 
       expect(person.name).to eq(name)
     end
@@ -30,7 +30,7 @@ describe Gitlab::LDAP::Person do
     it 'returns the value of mail, if present' do
       mail = 'john@example.com'
       entry['mail'] = mail
-      person = Gitlab::LDAP::Person.new(entry, 'ldapmain')
+      person = described_class.new(entry, 'ldapmain')
 
       expect(person.email).to eq([mail])
     end
@@ -38,7 +38,7 @@ describe Gitlab::LDAP::Person do
     it 'returns the value of userPrincipalName, if mail and email are not present' do
       user_principal_name = 'john.doe@example.com'
       entry['userPrincipalName'] = user_principal_name
-      person = Gitlab::LDAP::Person.new(entry, 'ldapmain')
+      person = described_class.new(entry, 'ldapmain')
 
       expect(person.email).to eq([user_principal_name])
     end
diff --git a/spec/lib/gitlab/metrics_spec.rb b/spec/lib/gitlab/metrics_spec.rb
index ab6e311b1e80b4034febc97db91c69f4393a8bfd..208a8d028cd604c6bfe1bee390eb871ab1b1906a 100644
--- a/spec/lib/gitlab/metrics_spec.rb
+++ b/spec/lib/gitlab/metrics_spec.rb
@@ -20,7 +20,7 @@ describe Gitlab::Metrics do
 
       expect(pool).to receive(:with).and_yield(connection)
       expect(connection).to receive(:write_points).with(an_instance_of(Array))
-      expect(Gitlab::Metrics).to receive(:pool).and_return(pool)
+      expect(described_class).to receive(:pool).and_return(pool)
 
       described_class.submit_metrics([{ 'series' => 'kittens', 'tags' => {} }])
     end
@@ -64,7 +64,7 @@ describe Gitlab::Metrics do
   describe '.measure' do
     context 'without a transaction' do
       it 'returns the return value of the block' do
-        val = Gitlab::Metrics.measure(:foo) { 10 }
+        val = described_class.measure(:foo) { 10 }
 
         expect(val).to eq(10)
       end
@@ -74,7 +74,7 @@ describe Gitlab::Metrics do
       let(:transaction) { Gitlab::Metrics::Transaction.new }
 
       before do
-        allow(Gitlab::Metrics).to receive(:current_transaction).
+        allow(described_class).to receive(:current_transaction).
           and_return(transaction)
       end
 
@@ -88,11 +88,11 @@ describe Gitlab::Metrics do
         expect(transaction).to receive(:increment).
           with('foo_call_count', 1)
 
-        Gitlab::Metrics.measure(:foo) { 10 }
+        described_class.measure(:foo) { 10 }
       end
 
       it 'returns the return value of the block' do
-        val = Gitlab::Metrics.measure(:foo) { 10 }
+        val = described_class.measure(:foo) { 10 }
 
         expect(val).to eq(10)
       end
@@ -105,7 +105,7 @@ describe Gitlab::Metrics do
         expect_any_instance_of(Gitlab::Metrics::Transaction).
           not_to receive(:add_tag)
 
-        Gitlab::Metrics.tag_transaction(:foo, 'bar')
+        described_class.tag_transaction(:foo, 'bar')
       end
     end
 
@@ -113,13 +113,13 @@ describe Gitlab::Metrics do
       let(:transaction) { Gitlab::Metrics::Transaction.new }
 
       it 'adds the tag to the transaction' do
-        expect(Gitlab::Metrics).to receive(:current_transaction).
+        expect(described_class).to receive(:current_transaction).
           and_return(transaction)
 
         expect(transaction).to receive(:add_tag).
           with(:foo, 'bar')
 
-        Gitlab::Metrics.tag_transaction(:foo, 'bar')
+        described_class.tag_transaction(:foo, 'bar')
       end
     end
   end
@@ -130,7 +130,7 @@ describe Gitlab::Metrics do
         expect_any_instance_of(Gitlab::Metrics::Transaction).
           not_to receive(:action=)
 
-        Gitlab::Metrics.action = 'foo'
+        described_class.action = 'foo'
       end
     end
 
@@ -138,12 +138,12 @@ describe Gitlab::Metrics do
       it 'sets the action of a transaction' do
         trans = Gitlab::Metrics::Transaction.new
 
-        expect(Gitlab::Metrics).to receive(:current_transaction).
+        expect(described_class).to receive(:current_transaction).
           and_return(trans)
 
         expect(trans).to receive(:action=).with('foo')
 
-        Gitlab::Metrics.action = 'foo'
+        described_class.action = 'foo'
       end
     end
   end
@@ -160,7 +160,7 @@ describe Gitlab::Metrics do
         expect_any_instance_of(Gitlab::Metrics::Transaction).
           not_to receive(:add_event)
 
-        Gitlab::Metrics.add_event(:meow)
+        described_class.add_event(:meow)
       end
     end
 
@@ -170,10 +170,10 @@ describe Gitlab::Metrics do
 
         expect(transaction).to receive(:add_event).with(:meow)
 
-        expect(Gitlab::Metrics).to receive(:current_transaction).
+        expect(described_class).to receive(:current_transaction).
           and_return(transaction)
 
-        Gitlab::Metrics.add_event(:meow)
+        described_class.add_event(:meow)
       end
     end
   end
diff --git a/spec/lib/gitlab/regex_spec.rb b/spec/lib/gitlab/regex_spec.rb
index 127cd8c78d8916496642fcf24aff2584439ca50b..72e947f2cc2bda20b3ffd2be32e87803350d076c 100644
--- a/spec/lib/gitlab/regex_spec.rb
+++ b/spec/lib/gitlab/regex_spec.rb
@@ -45,8 +45,8 @@ describe Gitlab::Regex, lib: true do
     it { is_expected.not_to match('foo-') }
   end
 
-  describe 'FULL_NAMESPACE_REGEX_STR' do
-    subject { %r{\A#{Gitlab::Regex::FULL_NAMESPACE_REGEX_STR}\z} }
+  describe '.full_namespace_regex' do
+    subject { described_class.full_namespace_regex }
 
     it { is_expected.to match('gitlab.org') }
     it { is_expected.to match('gitlab.org/gitlab-git') }
diff --git a/spec/lib/gitlab/sidekiq_throttler_spec.rb b/spec/lib/gitlab/sidekiq_throttler_spec.rb
index ff32e0e699decb9e85fa871b2add0596f3c91bbd..6374ac80207b08c878fde7eb616ae7a8b8f90f20 100644
--- a/spec/lib/gitlab/sidekiq_throttler_spec.rb
+++ b/spec/lib/gitlab/sidekiq_throttler_spec.rb
@@ -13,14 +13,14 @@ describe Gitlab::SidekiqThrottler do
 
   describe '#execute!' do
     it 'sets limits on the selected queues' do
-      Gitlab::SidekiqThrottler.execute!
+      described_class.execute!
 
       expect(Sidekiq::Queue['build'].limit).to eq 4
       expect(Sidekiq::Queue['project_cache'].limit).to eq 4
     end
 
     it 'does not set limits on other queues' do
-      Gitlab::SidekiqThrottler.execute!
+      described_class.execute!
 
       expect(Sidekiq::Queue['merge'].limit).to be_nil
     end
diff --git a/spec/lib/gitlab/slash_commands/dsl_spec.rb b/spec/lib/gitlab/slash_commands/dsl_spec.rb
index 26217a0e3b2c6d29ece057cc64e5b8166553eb4d..2763d950716276727976d1d3ea534b11ccaa9729 100644
--- a/spec/lib/gitlab/slash_commands/dsl_spec.rb
+++ b/spec/lib/gitlab/slash_commands/dsl_spec.rb
@@ -3,7 +3,7 @@ require 'spec_helper'
 describe Gitlab::SlashCommands::Dsl do
   before :all do
     DummyClass = Struct.new(:project) do
-      include Gitlab::SlashCommands::Dsl
+      include Gitlab::SlashCommands::Dsl # rubocop:disable RSpec/DescribedClass
 
       desc 'A command with no args'
       command :no_args, :none do
diff --git a/spec/lib/gitlab/template/gitignore_template_spec.rb b/spec/lib/gitlab/template/gitignore_template_spec.rb
index 9750a012e22dc877a07426187c3e11d417b2077e..97797f42aaab9a8b3f23036d7bf754ab43940871 100644
--- a/spec/lib/gitlab/template/gitignore_template_spec.rb
+++ b/spec/lib/gitlab/template/gitignore_template_spec.rb
@@ -24,7 +24,7 @@ describe Gitlab::Template::GitignoreTemplate do
     it 'returns the Gitignore object of a valid file' do
       ruby = subject.find('Ruby')
 
-      expect(ruby).to be_a Gitlab::Template::GitignoreTemplate
+      expect(ruby).to be_a described_class
       expect(ruby.name).to eq('Ruby')
     end
   end
diff --git a/spec/lib/gitlab/template/gitlab_ci_yml_template_spec.rb b/spec/lib/gitlab/template/gitlab_ci_yml_template_spec.rb
index e3b8321eda3907f61eeea3fdb25cfc5263cb40c7..6541326d1def4b20e264aa043640bf4d91d43abb 100644
--- a/spec/lib/gitlab/template/gitlab_ci_yml_template_spec.rb
+++ b/spec/lib/gitlab/template/gitlab_ci_yml_template_spec.rb
@@ -25,7 +25,7 @@ describe Gitlab::Template::GitlabCiYmlTemplate do
     it 'returns the GitlabCiYml object of a valid file' do
       ruby = subject.find('Ruby')
 
-      expect(ruby).to be_a Gitlab::Template::GitlabCiYmlTemplate
+      expect(ruby).to be_a described_class
       expect(ruby.name).to eq('Ruby')
     end
   end
diff --git a/spec/lib/gitlab/template/issue_template_spec.rb b/spec/lib/gitlab/template/issue_template_spec.rb
index 9213ced7b194d342c93c2bb949e9f1dcf23882fa..329d1d74970717d5310b4c689d8d4f1711c5029d 100644
--- a/spec/lib/gitlab/template/issue_template_spec.rb
+++ b/spec/lib/gitlab/template/issue_template_spec.rb
@@ -37,7 +37,7 @@ describe Gitlab::Template::IssueTemplate do
     it 'returns the issue object of a valid file' do
       ruby = subject.find('bug', project)
 
-      expect(ruby).to be_a Gitlab::Template::IssueTemplate
+      expect(ruby).to be_a described_class
       expect(ruby.name).to eq('bug')
     end
   end
diff --git a/spec/lib/gitlab/template/merge_request_template_spec.rb b/spec/lib/gitlab/template/merge_request_template_spec.rb
index 77dd3079e2254becb6d7fb6eafc6cd48b2e2af1d..2b0056d9bab9bbed3482d022a42a5dab01972232 100644
--- a/spec/lib/gitlab/template/merge_request_template_spec.rb
+++ b/spec/lib/gitlab/template/merge_request_template_spec.rb
@@ -37,7 +37,7 @@ describe Gitlab::Template::MergeRequestTemplate do
     it 'returns the merge request object of a valid file' do
       ruby = subject.find('bug', project)
 
-      expect(ruby).to be_a Gitlab::Template::MergeRequestTemplate
+      expect(ruby).to be_a described_class
       expect(ruby.name).to eq('bug')
     end
   end
diff --git a/spec/lib/gitlab/usage_data_spec.rb b/spec/lib/gitlab/usage_data_spec.rb
index 7f21288cf888cff21f62b7428f5d4523d4607eef..bf1dfe7f4129704f320fe780a97651c43430426d 100644
--- a/spec/lib/gitlab/usage_data_spec.rb
+++ b/spec/lib/gitlab/usage_data_spec.rb
@@ -6,7 +6,7 @@ describe Gitlab::UsageData do
   let!(:board) { create(:board, project: project) }
 
   describe '#data' do
-    subject { Gitlab::UsageData.data }
+    subject { described_class.data }
 
     it "gathers usage data" do
       expect(subject.keys).to match_array(%i(
@@ -58,7 +58,7 @@ describe Gitlab::UsageData do
   end
 
   describe '#license_usage_data' do
-    subject { Gitlab::UsageData.license_usage_data }
+    subject { described_class.license_usage_data }
 
     it "gathers license data" do
       expect(subject[:uuid]).to eq(current_application_settings.uuid)
diff --git a/spec/mailers/notify_spec.rb b/spec/mailers/notify_spec.rb
index e6f0a3b592010b2e5c83ef4258aed832f6458c9c..9f12e40d808e5b971542b09ccccf7fde30b05c53 100644
--- a/spec/mailers/notify_spec.rb
+++ b/spec/mailers/notify_spec.rb
@@ -40,7 +40,7 @@ describe Notify do
         let(:issue_with_description) { create(:issue, author: current_user, assignee: assignee, project: project, description: 'My awesome description') }
 
         describe 'that are new' do
-          subject { Notify.new_issue_email(issue.assignee_id, issue.id) }
+          subject { described_class.new_issue_email(issue.assignee_id, issue.id) }
 
           it_behaves_like 'an assignee email'
           it_behaves_like 'an email starting a new thread with reply-by-email enabled' do
@@ -69,7 +69,7 @@ describe Notify do
         end
 
         describe 'that are new with a description' do
-          subject { Notify.new_issue_email(issue_with_description.assignee_id, issue_with_description.id) }
+          subject { described_class.new_issue_email(issue_with_description.assignee_id, issue_with_description.id) }
 
           it_behaves_like 'it should show Gmail Actions View Issue link'
 
@@ -79,7 +79,7 @@ describe Notify do
         end
 
         describe 'that have been reassigned' do
-          subject { Notify.reassigned_issue_email(recipient.id, issue.id, previous_assignee.id, current_user.id) }
+          subject { described_class.reassigned_issue_email(recipient.id, issue.id, previous_assignee.id, current_user.id) }
 
           it_behaves_like 'a multiple recipients email'
           it_behaves_like 'an answer to an existing thread with reply-by-email enabled' do
@@ -105,7 +105,7 @@ describe Notify do
         end
 
         describe 'that have been relabeled' do
-          subject { Notify.relabeled_issue_email(recipient.id, issue.id, %w[foo bar baz], current_user.id) }
+          subject { described_class.relabeled_issue_email(recipient.id, issue.id, %w[foo bar baz], current_user.id) }
 
           it_behaves_like 'a multiple recipients email'
           it_behaves_like 'an answer to an existing thread with reply-by-email enabled' do
@@ -132,7 +132,7 @@ describe Notify do
 
         describe 'status changed' do
           let(:status) { 'closed' }
-          subject { Notify.issue_status_changed_email(recipient.id, issue.id, status, current_user.id) }
+          subject { described_class.issue_status_changed_email(recipient.id, issue.id, status, current_user.id) }
 
           it_behaves_like 'an answer to an existing thread with reply-by-email enabled' do
             let(:model) { issue }
@@ -158,7 +158,7 @@ describe Notify do
 
         describe 'moved to another project' do
           let(:new_issue) { create(:issue) }
-          subject { Notify.issue_moved_email(recipient, issue, new_issue, current_user) }
+          subject { described_class.issue_moved_email(recipient, issue, new_issue, current_user) }
 
           it_behaves_like 'an answer to an existing thread with reply-by-email enabled' do
             let(:model) { issue }
@@ -190,7 +190,7 @@ describe Notify do
         let(:merge_request_with_description) { create(:merge_request, author: current_user, assignee: assignee, source_project: project, target_project: project, description: 'My awesome description') }
 
         describe 'that are new' do
-          subject { Notify.new_merge_request_email(merge_request.assignee_id, merge_request.id) }
+          subject { described_class.new_merge_request_email(merge_request.assignee_id, merge_request.id) }
 
           it_behaves_like 'an assignee email'
           it_behaves_like 'an email starting a new thread with reply-by-email enabled' do
@@ -221,7 +221,7 @@ describe Notify do
         end
 
         describe 'that are new with a description' do
-          subject { Notify.new_merge_request_email(merge_request_with_description.assignee_id, merge_request_with_description.id) }
+          subject { described_class.new_merge_request_email(merge_request_with_description.assignee_id, merge_request_with_description.id) }
 
           it_behaves_like 'it should show Gmail Actions View Merge request link'
           it_behaves_like "an unsubscribeable thread"
@@ -232,7 +232,7 @@ describe Notify do
         end
 
         describe 'that are reassigned' do
-          subject { Notify.reassigned_merge_request_email(recipient.id, merge_request.id, previous_assignee.id, current_user.id) }
+          subject { described_class.reassigned_merge_request_email(recipient.id, merge_request.id, previous_assignee.id, current_user.id) }
 
           it_behaves_like 'a multiple recipients email'
           it_behaves_like 'an answer to an existing thread with reply-by-email enabled' do
@@ -258,7 +258,7 @@ describe Notify do
         end
 
         describe 'that have been relabeled' do
-          subject { Notify.relabeled_merge_request_email(recipient.id, merge_request.id, %w[foo bar baz], current_user.id) }
+          subject { described_class.relabeled_merge_request_email(recipient.id, merge_request.id, %w[foo bar baz], current_user.id) }
 
           it_behaves_like 'a multiple recipients email'
           it_behaves_like 'an answer to an existing thread with reply-by-email enabled' do
@@ -283,7 +283,7 @@ describe Notify do
 
         describe 'status changed' do
           let(:status) { 'reopened' }
-          subject { Notify.merge_request_status_email(recipient.id, merge_request.id, status, current_user.id) }
+          subject { described_class.merge_request_status_email(recipient.id, merge_request.id, status, current_user.id) }
 
           it_behaves_like 'an answer to an existing thread with reply-by-email enabled' do
             let(:model) { merge_request }
@@ -308,7 +308,7 @@ describe Notify do
         end
 
         describe 'that are merged' do
-          subject { Notify.merged_merge_request_email(recipient.id, merge_request.id, merge_author.id) }
+          subject { described_class.merged_merge_request_email(recipient.id, merge_request.id, merge_author.id) }
 
           it_behaves_like 'a multiple recipients email'
           it_behaves_like 'an answer to an existing thread with reply-by-email enabled' do
@@ -337,7 +337,7 @@ describe Notify do
     describe 'project was moved' do
       let(:project) { create(:empty_project) }
       let(:user) { create(:user) }
-      subject { Notify.project_was_moved_email(project.id, user.id, "gitlab/gitlab") }
+      subject { described_class.project_was_moved_email(project.id, user.id, "gitlab/gitlab") }
 
       it_behaves_like 'an email sent from GitLab'
       it_behaves_like 'it should not have Gmail Actions links'
@@ -363,7 +363,7 @@ describe Notify do
           project.request_access(user)
           project.requesters.find_by(user_id: user.id)
         end
-        subject { Notify.member_access_requested_email('project', project_member.id) }
+        subject { described_class.member_access_requested_email('project', project_member.id) }
 
         it_behaves_like 'an email sent from GitLab'
         it_behaves_like 'it should not have Gmail Actions links'
@@ -390,7 +390,7 @@ describe Notify do
           project.request_access(user)
           project.requesters.find_by(user_id: user.id)
         end
-        subject { Notify.member_access_requested_email('project', project_member.id) }
+        subject { described_class.member_access_requested_email('project', project_member.id) }
 
         it_behaves_like 'an email sent from GitLab'
         it_behaves_like 'it should not have Gmail Actions links'
@@ -416,7 +416,7 @@ describe Notify do
         project.request_access(user)
         project.requesters.find_by(user_id: user.id)
       end
-      subject { Notify.member_access_denied_email('project', project.id, user.id) }
+      subject { described_class.member_access_denied_email('project', project.id, user.id) }
 
       it_behaves_like 'an email sent from GitLab'
       it_behaves_like 'it should not have Gmail Actions links'
@@ -434,7 +434,7 @@ describe Notify do
       let(:project) { create(:empty_project, :public, :access_requestable, namespace: owner.namespace) }
       let(:user) { create(:user) }
       let(:project_member) { create(:project_member, project: project, user: user) }
-      subject { Notify.member_access_granted_email('project', project_member.id) }
+      subject { described_class.member_access_granted_email('project', project_member.id) }
 
       it_behaves_like 'an email sent from GitLab'
       it_behaves_like 'it should not have Gmail Actions links'
@@ -465,7 +465,7 @@ describe Notify do
       let(:master) { create(:user).tap { |u| project.team << [u, :master] } }
       let(:project_member) { invite_to_project(project, inviter: master) }
 
-      subject { Notify.member_invited_email('project', project_member.id, project_member.invite_token) }
+      subject { described_class.member_invited_email('project', project_member.id, project_member.invite_token) }
 
       it_behaves_like 'an email sent from GitLab'
       it_behaves_like 'it should not have Gmail Actions links'
@@ -490,7 +490,7 @@ describe Notify do
         invitee
       end
 
-      subject { Notify.member_invite_accepted_email('project', project_member.id) }
+      subject { described_class.member_invite_accepted_email('project', project_member.id) }
 
       it_behaves_like 'an email sent from GitLab'
       it_behaves_like 'it should not have Gmail Actions links'
@@ -514,7 +514,7 @@ describe Notify do
         invitee
       end
 
-      subject { Notify.member_invite_declined_email('project', project.id, project_member.invite_email, master.id) }
+      subject { described_class.member_invite_declined_email('project', project.id, project_member.invite_email, master.id) }
 
       it_behaves_like 'an email sent from GitLab'
       it_behaves_like 'it should not have Gmail Actions links'
@@ -574,7 +574,7 @@ describe Notify do
 
         before(:each) { allow(note).to receive(:noteable).and_return(commit) }
 
-        subject { Notify.note_commit_email(recipient.id, note.id) }
+        subject { described_class.note_commit_email(recipient.id, note.id) }
 
         it_behaves_like 'a note email'
         it_behaves_like 'an answer to an existing thread with reply-by-email enabled' do
@@ -596,7 +596,7 @@ describe Notify do
         let(:note_on_merge_request_path) { namespace_project_merge_request_path(project.namespace, project, merge_request, anchor: "note_#{note.id}") }
         before(:each) { allow(note).to receive(:noteable).and_return(merge_request) }
 
-        subject { Notify.note_merge_request_email(recipient.id, note.id) }
+        subject { described_class.note_merge_request_email(recipient.id, note.id) }
 
         it_behaves_like 'a note email'
         it_behaves_like 'an answer to an existing thread with reply-by-email enabled' do
@@ -618,7 +618,7 @@ describe Notify do
         let(:note_on_issue_path) { namespace_project_issue_path(project.namespace, project, issue, anchor: "note_#{note.id}") }
         before(:each) { allow(note).to receive(:noteable).and_return(issue) }
 
-        subject { Notify.note_issue_email(recipient.id, note.id) }
+        subject { described_class.note_issue_email(recipient.id, note.id) }
 
         it_behaves_like 'a note email'
         it_behaves_like 'an answer to an existing thread with reply-by-email enabled' do
@@ -680,7 +680,7 @@ describe Notify do
 
         before(:each) { allow(note).to receive(:noteable).and_return(commit) }
 
-        subject { Notify.note_commit_email(recipient.id, note.id) }
+        subject { described_class.note_commit_email(recipient.id, note.id) }
 
         it_behaves_like 'a discussion note email', :discussion_note_on_commit
         it_behaves_like 'an answer to an existing thread with reply-by-email enabled' do
@@ -704,7 +704,7 @@ describe Notify do
         let(:note_on_merge_request_path) { namespace_project_merge_request_path(project.namespace, project, merge_request, anchor: "note_#{note.id}") }
         before(:each) { allow(note).to receive(:noteable).and_return(merge_request) }
 
-        subject { Notify.note_merge_request_email(recipient.id, note.id) }
+        subject { described_class.note_merge_request_email(recipient.id, note.id) }
 
         it_behaves_like 'a discussion note email', :discussion_note_on_merge_request
         it_behaves_like 'an answer to an existing thread with reply-by-email enabled' do
@@ -728,7 +728,7 @@ describe Notify do
         let(:note_on_issue_path) { namespace_project_issue_path(project.namespace, project, issue, anchor: "note_#{note.id}") }
         before(:each) { allow(note).to receive(:noteable).and_return(issue) }
 
-        subject { Notify.note_issue_email(recipient.id, note.id) }
+        subject { described_class.note_issue_email(recipient.id, note.id) }
 
         it_behaves_like 'a discussion note email', :discussion_note_on_issue
         it_behaves_like 'an answer to an existing thread with reply-by-email enabled' do
@@ -798,7 +798,7 @@ describe Notify do
         let(:commit) { project.commit }
         let(:note) { create(:diff_note_on_commit) }
 
-        subject { Notify.note_commit_email(recipient.id, note.id) }
+        subject { described_class.note_commit_email(recipient.id, note.id) }
 
         it_behaves_like 'an email for a note on a diff discussion', :diff_note_on_commit
         it_behaves_like 'it should show Gmail Actions View Commit link'
@@ -809,7 +809,7 @@ describe Notify do
         let(:merge_request) { create(:merge_request, source_project: project, target_project: project) }
         let(:note) { create(:diff_note_on_merge_request) }
 
-        subject { Notify.note_merge_request_email(recipient.id, note.id) }
+        subject { described_class.note_merge_request_email(recipient.id, note.id) }
 
         it_behaves_like 'an email for a note on a diff discussion', :diff_note_on_merge_request
         it_behaves_like 'it should show Gmail Actions View Merge request link'
@@ -826,7 +826,7 @@ describe Notify do
         group.request_access(user)
         group.requesters.find_by(user_id: user.id)
       end
-      subject { Notify.member_access_requested_email('group', group_member.id) }
+      subject { described_class.member_access_requested_email('group', group_member.id) }
 
       it_behaves_like 'an email sent from GitLab'
       it_behaves_like 'it should not have Gmail Actions links'
@@ -847,7 +847,7 @@ describe Notify do
         group.request_access(user)
         group.requesters.find_by(user_id: user.id)
       end
-      subject { Notify.member_access_denied_email('group', group.id, user.id) }
+      subject { described_class.member_access_denied_email('group', group.id, user.id) }
 
       it_behaves_like 'an email sent from GitLab'
       it_behaves_like 'it should not have Gmail Actions links'
@@ -865,7 +865,7 @@ describe Notify do
       let(:user) { create(:user) }
       let(:group_member) { create(:group_member, group: group, user: user) }
 
-      subject { Notify.member_access_granted_email('group', group_member.id) }
+      subject { described_class.member_access_granted_email('group', group_member.id) }
 
       it_behaves_like 'an email sent from GitLab'
       it_behaves_like 'it should not have Gmail Actions links'
@@ -896,7 +896,7 @@ describe Notify do
       let(:owner) { create(:user).tap { |u| group.add_user(u, Gitlab::Access::OWNER) } }
       let(:group_member) { invite_to_group(group, inviter: owner) }
 
-      subject { Notify.member_invited_email('group', group_member.id, group_member.invite_token) }
+      subject { described_class.member_invited_email('group', group_member.id, group_member.invite_token) }
 
       it_behaves_like 'an email sent from GitLab'
       it_behaves_like 'it should not have Gmail Actions links'
@@ -921,7 +921,7 @@ describe Notify do
         invitee
       end
 
-      subject { Notify.member_invite_accepted_email('group', group_member.id) }
+      subject { described_class.member_invite_accepted_email('group', group_member.id) }
 
       it_behaves_like 'an email sent from GitLab'
       it_behaves_like 'it should not have Gmail Actions links'
@@ -945,7 +945,7 @@ describe Notify do
         invitee
       end
 
-      subject { Notify.member_invite_declined_email('group', group.id, group_member.invite_email, owner.id) }
+      subject { described_class.member_invite_declined_email('group', group.id, group_member.invite_email, owner.id) }
 
       it_behaves_like 'an email sent from GitLab'
       it_behaves_like 'it should not have Gmail Actions links'
@@ -994,7 +994,7 @@ describe Notify do
     let(:user) { create(:user) }
     let(:tree_path) { namespace_project_tree_path(project.namespace, project, "empty-branch") }
 
-    subject { Notify.repository_push_email(project.id, author_id: user.id, ref: 'refs/heads/empty-branch', action: :create) }
+    subject { described_class.repository_push_email(project.id, author_id: user.id, ref: 'refs/heads/empty-branch', action: :create) }
 
     it_behaves_like 'it should not have Gmail Actions links'
     it_behaves_like 'a user cannot unsubscribe through footer link'
@@ -1020,7 +1020,7 @@ describe Notify do
     let(:user) { create(:user) }
     let(:tree_path) { namespace_project_tree_path(project.namespace, project, "v1.0") }
 
-    subject { Notify.repository_push_email(project.id, author_id: user.id, ref: 'refs/tags/v1.0', action: :create) }
+    subject { described_class.repository_push_email(project.id, author_id: user.id, ref: 'refs/tags/v1.0', action: :create) }
 
     it_behaves_like 'it should not have Gmail Actions links'
     it_behaves_like "a user cannot unsubscribe through footer link"
@@ -1045,7 +1045,7 @@ describe Notify do
     let(:example_site_path) { root_path }
     let(:user) { create(:user) }
 
-    subject { Notify.repository_push_email(project.id, author_id: user.id, ref: 'refs/heads/master', action: :delete) }
+    subject { described_class.repository_push_email(project.id, author_id: user.id, ref: 'refs/heads/master', action: :delete) }
 
     it_behaves_like 'it should not have Gmail Actions links'
     it_behaves_like 'a user cannot unsubscribe through footer link'
@@ -1067,7 +1067,7 @@ describe Notify do
     let(:example_site_path) { root_path }
     let(:user) { create(:user) }
 
-    subject { Notify.repository_push_email(project.id, author_id: user.id, ref: 'refs/tags/v1.0', action: :delete) }
+    subject { described_class.repository_push_email(project.id, author_id: user.id, ref: 'refs/tags/v1.0', action: :delete) }
 
     it_behaves_like 'it should not have Gmail Actions links'
     it_behaves_like 'a user cannot unsubscribe through footer link'
@@ -1096,7 +1096,7 @@ describe Notify do
     let(:send_from_committer_email) { false }
     let(:diff_refs) { Gitlab::Diff::DiffRefs.new(base_sha: project.merge_base_commit(sample_image_commit.id, sample_commit.id).id, head_sha: sample_commit.id) }
 
-    subject { Notify.repository_push_email(project.id, author_id: user.id, ref: 'refs/heads/master', action: :push, compare: compare, reverse_compare: false, diff_refs: diff_refs, send_from_committer_email: send_from_committer_email) }
+    subject { described_class.repository_push_email(project.id, author_id: user.id, ref: 'refs/heads/master', action: :push, compare: compare, reverse_compare: false, diff_refs: diff_refs, send_from_committer_email: send_from_committer_email) }
 
     it_behaves_like 'it should not have Gmail Actions links'
     it_behaves_like 'a user cannot unsubscribe through footer link'
@@ -1189,7 +1189,7 @@ describe Notify do
     let(:diff_path) { namespace_project_commit_path(project.namespace, project, commits.first) }
     let(:diff_refs) { Gitlab::Diff::DiffRefs.new(base_sha: project.merge_base_commit(sample_image_commit.id, sample_commit.id).id, head_sha: sample_commit.id) }
 
-    subject { Notify.repository_push_email(project.id, author_id: user.id, ref: 'refs/heads/master', action: :push, compare: compare, diff_refs: diff_refs) }
+    subject { described_class.repository_push_email(project.id, author_id: user.id, ref: 'refs/heads/master', action: :push, compare: compare, diff_refs: diff_refs) }
 
     it_behaves_like 'it should show Gmail Actions View Commit link'
     it_behaves_like 'a user cannot unsubscribe through footer link'
@@ -1215,7 +1215,7 @@ describe Notify do
   describe 'HTML emails setting' do
     let(:project) { create(:empty_project) }
     let(:user) { create(:user) }
-    let(:multipart_mail) { Notify.project_was_moved_email(project.id, user.id, "gitlab/gitlab") }
+    let(:multipart_mail) { described_class.project_was_moved_email(project.id, user.id, "gitlab/gitlab") }
 
     context 'when disabled' do
       it 'only sends the text template' do
diff --git a/spec/models/concerns/cache_markdown_field_spec.rb b/spec/models/concerns/cache_markdown_field_spec.rb
index 4edafbc4e32495234b5cf53eb61b3c77a9ca06c7..40bbb10eaac45f54ad2fa30c111851bd453226f9 100644
--- a/spec/models/concerns/cache_markdown_field_spec.rb
+++ b/spec/models/concerns/cache_markdown_field_spec.rb
@@ -170,6 +170,12 @@ describe CacheMarkdownField do
 
       is_expected.to be_truthy
     end
+
+    it 'returns false if the markdown field is set but the html is not' do
+      thing.foo_html = nil
+
+      is_expected.to be_falsy
+    end
   end
 
   describe '#refresh_markdown_cache!' do
diff --git a/spec/models/group_spec.rb b/spec/models/group_spec.rb
index 8ffde6f7fbb59fe2c6e92c423d4600eff85753e8..a11805926cc53140ad3665766f9c86d708fff938 100644
--- a/spec/models/group_spec.rb
+++ b/spec/models/group_spec.rb
@@ -57,6 +57,32 @@ describe Group, models: true do
     it { is_expected.not_to validate_presence_of :owner }
     it { is_expected.to validate_presence_of :two_factor_grace_period }
     it { is_expected.to validate_numericality_of(:two_factor_grace_period).is_greater_than_or_equal_to(0) }
+
+    describe 'path validation' do
+      it 'rejects paths reserved on the root namespace when the group has no parent' do
+        group = build(:group, path: 'api')
+
+        expect(group).not_to be_valid
+      end
+
+      it 'allows root paths when the group has a parent' do
+        group = build(:group, path: 'api', parent: create(:group))
+
+        expect(group).to be_valid
+      end
+
+      it 'rejects any wildcard paths when not a top level group' do
+        group = build(:group, path: 'tree', parent: create(:group))
+
+        expect(group).not_to be_valid
+      end
+
+      it 'rejects reserved group paths' do
+        group = build(:group, path: 'activity', parent: create(:group))
+
+        expect(group).not_to be_valid
+      end
+    end
   end
 
   describe '.visible_to_user' do
diff --git a/spec/models/namespace_spec.rb b/spec/models/namespace_spec.rb
index e406d0a16bdf9360e040b98e24f087ec191655b6..8624616316c87e2631a92a0a2ec51cd45b0ede90 100644
--- a/spec/models/namespace_spec.rb
+++ b/spec/models/namespace_spec.rb
@@ -34,6 +34,13 @@ describe Namespace, models: true do
         let(:group) { build(:group, :nested, path: 'tree') }
 
         it { expect(group).not_to be_valid }
+
+        it 'rejects nested paths' do
+          parent = create(:group, :nested, path: 'environments')
+          namespace = build(:project, path: 'folders', namespace: parent)
+
+          expect(namespace).not_to be_valid
+        end
       end
 
       context 'top-level group' do
@@ -47,6 +54,7 @@ describe Namespace, models: true do
   describe "Respond to" do
     it { is_expected.to respond_to(:human_name) }
     it { is_expected.to respond_to(:to_param) }
+    it { is_expected.to respond_to(:has_parent?) }
   end
 
   describe '#to_param' do
diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb
index d9244657953c2f242182f3b07b90cd452f58940e..36ce3070a6eb026b4dd36b3afd94cbd24547cd34 100644
--- a/spec/models/project_spec.rb
+++ b/spec/models/project_spec.rb
@@ -253,6 +253,34 @@ describe Project, models: true do
         expect(new_project.errors.full_messages.first).to eq('The project is still being deleted. Please try again later.')
       end
     end
+
+    describe 'path validation' do
+      it 'allows paths reserved on the root namespace' do
+        project = build(:project, path: 'api')
+
+        expect(project).to be_valid
+      end
+
+      it 'rejects paths reserved on another level' do
+        project = build(:project, path: 'tree')
+
+        expect(project).not_to be_valid
+      end
+
+      it 'rejects nested paths' do
+        parent = create(:group, :nested, path: 'environments')
+        project = build(:project, path: 'folders', namespace: parent)
+
+        expect(project).not_to be_valid
+      end
+
+      it 'allows a reserved group name' do
+        parent = create(:group)
+        project = build(:project, path: 'avatar', namespace: parent)
+
+        expect(project).to be_valid
+      end
+    end
   end
 
   describe 'default_scope' do
@@ -1878,4 +1906,23 @@ describe Project, models: true do
       expect(project.pipeline_status).to be_loaded
     end
   end
+
+  describe '#append_or_update_attribute' do
+    let(:project) { create(:project) }
+
+    it 'shows full error updating an invalid MR' do
+      error_message = 'Failed to replace merge_requests because one or more of the new records could not be saved.'\
+                      ' Validate fork Source project is not a fork of the target project'
+
+      expect { project.append_or_update_attribute(:merge_requests, [create(:merge_request)]) }.
+        to raise_error(ActiveRecord::RecordNotSaved, error_message)
+    end
+
+    it 'updates the project succesfully' do
+      merge_request = create(:merge_request, target_project: project, source_project: project)
+
+      expect { project.append_or_update_attribute(:merge_requests, [merge_request]) }.
+        not_to raise_error
+    end
+  end
 end
diff --git a/spec/models/repository_spec.rb b/spec/models/repository_spec.rb
index f6846cc1b2fe3c50d342a3b3992e6a8712d6db03..5216764a82dd6e79fe6130f4bfe18a9e20798c2e 100644
--- a/spec/models/repository_spec.rb
+++ b/spec/models/repository_spec.rb
@@ -1379,12 +1379,22 @@ describe Repository, models: true do
   describe '#branch_count' do
     it 'returns the number of branches' do
       expect(repository.branch_count).to be_an(Integer)
+
+      # NOTE: Until rugged goes away, make sure rugged and gitaly are in sync
+      rugged_count = repository.raw_repository.rugged.branches.count
+
+      expect(repository.branch_count).to eq(rugged_count)
     end
   end
 
   describe '#tag_count' do
     it 'returns the number of tags' do
       expect(repository.tag_count).to be_an(Integer)
+
+      # NOTE: Until rugged goes away, make sure rugged and gitaly are in sync
+      rugged_count = repository.raw_repository.rugged.tags.count
+
+      expect(repository.tag_count).to eq(rugged_count)
     end
   end
 
diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb
index 0bcebc275988b0bc43922e030615598fcdee4692..1c2df4c9d97415b9d74785c7206867e9e1e86da9 100644
--- a/spec/models/user_spec.rb
+++ b/spec/models/user_spec.rb
@@ -97,6 +97,18 @@ describe User, models: true do
         expect(user.errors.values).to eq [['dashboard is a reserved name']]
       end
 
+      it 'allows child names' do
+        user = build(:user, username: 'avatar')
+
+        expect(user).to be_valid
+      end
+
+      it 'allows wildcard names' do
+        user = build(:user, username: 'blob')
+
+        expect(user).to be_valid
+      end
+
       it 'validates uniqueness' do
         expect(subject).to validate_uniqueness_of(:username).case_insensitive
       end
diff --git a/spec/requests/api/helpers/internal_helpers_spec.rb b/spec/requests/api/helpers/internal_helpers_spec.rb
index f5265ea60ff4aee2d4f167df49d093100360d665..db716b340f1c3ef48029d68a70e31e116770c2a9 100644
--- a/spec/requests/api/helpers/internal_helpers_spec.rb
+++ b/spec/requests/api/helpers/internal_helpers_spec.rb
@@ -1,7 +1,7 @@
 require 'spec_helper'
 
 describe ::API::Helpers::InternalHelpers do
-  include ::API::Helpers::InternalHelpers
+  include described_class
 
   describe '.clean_project_path' do
     project = 'namespace/project'
diff --git a/spec/requests/api/helpers_spec.rb b/spec/requests/api/helpers_spec.rb
index 06c8eb1d0b7fad9d283971461ed5b0d4985d9846..ed392acc607a08f5c8b5c0244224c3083edab19c 100644
--- a/spec/requests/api/helpers_spec.rb
+++ b/spec/requests/api/helpers_spec.rb
@@ -2,7 +2,7 @@ require 'spec_helper'
 
 describe API::Helpers do
   include API::APIGuard::HelperMethods
-  include API::Helpers
+  include described_class
   include SentryHelper
 
   let(:user) { create(:user) }
diff --git a/spec/requests/api/merge_requests_spec.rb b/spec/requests/api/merge_requests_spec.rb
index c4bff1647b5a26a08d72182ab34c5afa50e72fbc..16e5efb2f5b444776ac6e1f6a0074fe790e47a4a 100644
--- a/spec/requests/api/merge_requests_spec.rb
+++ b/spec/requests/api/merge_requests_spec.rb
@@ -434,6 +434,19 @@ describe API::MergeRequests do
         expect(json_response['title']).to eq('Test merge_request')
       end
 
+      it 'returns 422 when target project has disabled merge requests' do
+        project.project_feature.update(merge_requests_access_level: 0)
+
+        post api("/projects/#{fork_project.id}/merge_requests", user2),
+             title: 'Test',
+             target_branch: 'master',
+             source_branch: 'markdown',
+             author: user2,
+             target_project_id: project.id
+
+        expect(response).to have_http_status(422)
+      end
+
       it "returns 400 when source_branch is missing" do
         post api("/projects/#{fork_project.id}/merge_requests", user2),
         title: 'Test merge_request', target_branch: "master", author: user2, target_project_id: project.id
diff --git a/spec/requests/api/v3/merge_requests_spec.rb b/spec/requests/api/v3/merge_requests_spec.rb
index 6c2950a6e6f7215ef2381c34c5cfa2097f8c1cdb..f6ff96be5665074b7546cc70b9d8caf185c04743 100644
--- a/spec/requests/api/v3/merge_requests_spec.rb
+++ b/spec/requests/api/v3/merge_requests_spec.rb
@@ -338,6 +338,19 @@ describe API::MergeRequests do
         expect(json_response['title']).to eq('Test merge_request')
       end
 
+      it "returns 422 when target project has disabled merge requests" do
+        project.project_feature.update(merge_requests_access_level: 0)
+
+        post v3_api("/projects/#{fork_project.id}/merge_requests", user2),
+             title: 'Test',
+             target_branch: "master",
+             source_branch: 'markdown',
+             author: user2,
+             target_project_id: project.id
+
+        expect(response).to have_http_status(422)
+      end
+
       it "returns 400 when source_branch is missing" do
         post v3_api("/projects/#{fork_project.id}/merge_requests", user2),
         title: 'Test merge_request', target_branch: "master", author: user2, target_project_id: project.id
diff --git a/spec/routing/admin_routing_spec.rb b/spec/routing/admin_routing_spec.rb
index 99c44bde1518509ec046d946591ef76260499bc4..e5fc0b676af4abcddab35e33e5098459fa2d52a6 100644
--- a/spec/routing/admin_routing_spec.rb
+++ b/spec/routing/admin_routing_spec.rb
@@ -71,13 +71,15 @@ describe Admin::ProjectsController, "routing" do
   end
 end
 
-# admin_hook_test GET    /admin/hooks/:hook_id/test(.:format) admin/hooks#test
+# admin_hook_test GET    /admin/hooks/:id/test(.:format)      admin/hooks#test
 #     admin_hooks GET    /admin/hooks(.:format)               admin/hooks#index
 #                 POST   /admin/hooks(.:format)               admin/hooks#create
 #      admin_hook DELETE /admin/hooks/:id(.:format)           admin/hooks#destroy
+#                 PUT    /admin/hooks/:id(.:format)           admin/hooks#update
+# edit_admin_hook GET    /admin/hooks/:id(.:format)           admin/hooks#edit
 describe Admin::HooksController, "routing" do
   it "to #test" do
-    expect(get("/admin/hooks/1/test")).to route_to('admin/hooks#test', hook_id: '1')
+    expect(get("/admin/hooks/1/test")).to route_to('admin/hooks#test', id: '1')
   end
 
   it "to #index" do
@@ -88,6 +90,14 @@ describe Admin::HooksController, "routing" do
     expect(post("/admin/hooks")).to route_to('admin/hooks#create')
   end
 
+  it "to #edit" do
+    expect(get("/admin/hooks/1/edit")).to route_to('admin/hooks#edit', id: '1')
+  end
+
+  it "to #update" do
+    expect(put("/admin/hooks/1")).to route_to('admin/hooks#update', id: '1')
+  end
+
   it "to #destroy" do
     expect(delete("/admin/hooks/1")).to route_to('admin/hooks#destroy', id: '1')
   end
diff --git a/spec/routing/project_routing_spec.rb b/spec/routing/project_routing_spec.rb
index a3de022d242e8dcfdce8203afae190d598a772be..163df072cf634972f0ffc72d2df1a691e849cf09 100644
--- a/spec/routing/project_routing_spec.rb
+++ b/spec/routing/project_routing_spec.rb
@@ -340,14 +340,16 @@ describe 'project routing' do
   # test_project_hook GET    /:project_id/hooks/:id/test(.:format) hooks#test
   #     project_hooks GET    /:project_id/hooks(.:format)          hooks#index
   #                   POST   /:project_id/hooks(.:format)          hooks#create
-  #      project_hook DELETE /:project_id/hooks/:id(.:format)      hooks#destroy
+  # edit_project_hook GET    /:project_id/hooks/:id/edit(.:format) hooks#edit
+  #      project_hook PUT    /:project_id/hooks/:id(.:format)      hooks#update
+  #                   DELETE /:project_id/hooks/:id(.:format)      hooks#destroy
   describe Projects::HooksController, 'routing' do
     it 'to #test' do
       expect(get('/gitlab/gitlabhq/hooks/1/test')).to route_to('projects/hooks#test', namespace_id: 'gitlab', project_id: 'gitlabhq', id: '1')
     end
 
     it_behaves_like 'RESTful project resources' do
-      let(:actions)    { [:index, :create, :destroy] }
+      let(:actions)    { [:index, :create, :destroy, :edit, :update] }
       let(:controller) { 'hooks' }
     end
   end
diff --git a/spec/serializers/deployment_entity_spec.rb b/spec/serializers/deployment_entity_spec.rb
index 95eca5463eb618266c219fc10d1d323ef83a531c..69355bcde42d1ef2d40702927643142924015062 100644
--- a/spec/serializers/deployment_entity_spec.rb
+++ b/spec/serializers/deployment_entity_spec.rb
@@ -3,25 +3,23 @@ require 'spec_helper'
 describe DeploymentEntity do
   let(:user) { create(:user) }
   let(:request) { double('request') }
+  let(:deployment) { create(:deployment) }
+  let(:entity) { described_class.new(deployment, request: request) }
+  subject { entity.as_json }
 
   before do
     allow(request).to receive(:user).and_return(user)
   end
 
-  let(:entity) do
-    described_class.new(deployment, request: request)
-  end
-
-  let(:deployment) { create(:deployment) }
-
-  subject { entity.as_json }
-
   it 'exposes internal deployment id'  do
     expect(subject).to include(:iid)
   end
 
   it 'exposes nested information about branch' do
     expect(subject[:ref][:name]).to eq 'master'
-    expect(subject[:ref][:ref_path]).not_to be_empty
+  end
+
+  it 'exposes creation date' do
+    expect(subject).to include(:created_at)
   end
 end
diff --git a/spec/serializers/status_entity_spec.rb b/spec/serializers/status_entity_spec.rb
index c94902dbab8f77d84d7fd64bc8a4550d1a95bbfa..3964b99808436de14955877e3d914b61252d2991 100644
--- a/spec/serializers/status_entity_spec.rb
+++ b/spec/serializers/status_entity_spec.rb
@@ -18,6 +18,12 @@ describe StatusEntity do
     it 'contains status details' do
       expect(subject).to include :text, :icon, :favicon, :label, :group
       expect(subject).to include :has_details, :details_path
+      expect(subject[:favicon]).to eq('/assets/ci_favicons/favicon_status_success.ico')
+    end
+
+    it 'contains a dev namespaced favicon if dev env' do
+      allow(Rails.env).to receive(:development?) { true }
+      expect(entity.as_json[:favicon]).to eq('/assets/ci_favicons/dev/favicon_status_success.ico')
     end
   end
 end
diff --git a/spec/services/merge_requests/build_service_spec.rb b/spec/services/merge_requests/build_service_spec.rb
index be9f9ea2dec3b658a77936c30d67ebc28d9ae1c4..6f9d1208b1d813d75f52fa7171fc1e36378d96a2 100644
--- a/spec/services/merge_requests/build_service_spec.rb
+++ b/spec/services/merge_requests/build_service_spec.rb
@@ -261,6 +261,16 @@ describe MergeRequests::BuildService, services: true do
       end
     end
 
+    context 'upstream project has disabled merge requests' do
+      let(:upstream_project) { create(:empty_project, :merge_requests_disabled) }
+      let(:project) { create(:empty_project, forked_from_project: upstream_project) }
+      let(:commits) { Commit.decorate([commit_1], project) }
+
+      it 'sets target project correctly' do
+        expect(merge_request.target_project).to eq(project)
+      end
+    end
+
     context 'target_project is set and accessible by current_user' do
       let(:target_project) { create(:project, :public, :repository)}
       let(:commits) { Commit.decorate([commit_1], project) }
diff --git a/spec/services/merge_requests/get_urls_service_spec.rb b/spec/services/merge_requests/get_urls_service_spec.rb
index 290e00ea1baa51838283870faef592aab6394ef6..4a7d8ab4c6cacee6d8385b14fab951d35fa7e889 100644
--- a/spec/services/merge_requests/get_urls_service_spec.rb
+++ b/spec/services/merge_requests/get_urls_service_spec.rb
@@ -2,7 +2,7 @@ require "spec_helper"
 
 describe MergeRequests::GetUrlsService do
   let(:project) { create(:project, :public, :repository) }
-  let(:service) { MergeRequests::GetUrlsService.new(project) }
+  let(:service) { described_class.new(project) }
   let(:source_branch) { "my_branch" }
   let(:new_merge_request_url) { "http://#{Gitlab.config.gitlab.host}/#{project.namespace.name}/#{project.path}/merge_requests/new?merge_request%5Bsource_branch%5D=#{source_branch}" }
   let(:show_merge_request_url) { "http://#{Gitlab.config.gitlab.host}/#{project.namespace.name}/#{project.path}/merge_requests/#{merge_request.iid}" }
@@ -89,7 +89,7 @@ describe MergeRequests::GetUrlsService do
       let!(:merge_request) { create(:merge_request, source_project: forked_project, target_project: project, source_branch: source_branch) }
       let(:changes) { existing_branch_changes }
       # Source project is now the forked one
-      let(:service) { MergeRequests::GetUrlsService.new(forked_project) }
+      let(:service) { described_class.new(forked_project) }
 
       before do
         allow(forked_project).to receive(:empty_repo?).and_return(false)
diff --git a/spec/services/merge_requests/merge_request_diff_cache_service_spec.rb b/spec/services/merge_requests/merge_request_diff_cache_service_spec.rb
index 35804d41b4605446de979e4e91459df3239493f0..935f4710851863018a370d7474c6e39e2c1a6701 100644
--- a/spec/services/merge_requests/merge_request_diff_cache_service_spec.rb
+++ b/spec/services/merge_requests/merge_request_diff_cache_service_spec.rb
@@ -1,7 +1,7 @@
 require 'spec_helper'
 
 describe MergeRequests::MergeRequestDiffCacheService do
-  let(:subject) { MergeRequests::MergeRequestDiffCacheService.new }
+  let(:subject) { described_class.new }
 
   describe '#execute' do
     it 'retrieves the diff files to cache the highlighted result' do
diff --git a/spec/services/merge_requests/resolve_service_spec.rb b/spec/services/merge_requests/resolve_service_spec.rb
index eaf7785e549e84a45feefc749d256a873910f2ec..3afd6b92900868746ee664bb0d68ddff92375aae 100644
--- a/spec/services/merge_requests/resolve_service_spec.rb
+++ b/spec/services/merge_requests/resolve_service_spec.rb
@@ -50,7 +50,7 @@ describe MergeRequests::ResolveService do
 
       context 'when the source and target project are the same' do
         before do
-          MergeRequests::ResolveService.new(project, user, params).execute(merge_request)
+          described_class.new(project, user, params).execute(merge_request)
         end
 
         it 'creates a commit with the message' do
@@ -75,7 +75,7 @@ describe MergeRequests::ResolveService do
         end
 
         before do
-          MergeRequests::ResolveService.new(fork_project, user, params).execute(merge_request_from_fork)
+          described_class.new(fork_project, user, params).execute(merge_request_from_fork)
         end
 
         it 'creates a commit with the message' do
@@ -115,7 +115,7 @@ describe MergeRequests::ResolveService do
       end
 
       before do
-        MergeRequests::ResolveService.new(project, user, params).execute(merge_request)
+        described_class.new(project, user, params).execute(merge_request)
       end
 
       it 'creates a commit with the message' do
@@ -154,7 +154,7 @@ describe MergeRequests::ResolveService do
         }
       end
 
-      let(:service) { MergeRequests::ResolveService.new(project, user, invalid_params) }
+      let(:service) { described_class.new(project, user, invalid_params) }
 
       it 'raises a MissingResolution error' do
         expect { service.execute(merge_request) }.
@@ -180,7 +180,7 @@ describe MergeRequests::ResolveService do
         }
       end
 
-      let(:service) { MergeRequests::ResolveService.new(project, user, invalid_params) }
+      let(:service) { described_class.new(project, user, invalid_params) }
 
       it 'raises a MissingResolution error' do
         expect { service.execute(merge_request) }.
@@ -202,7 +202,7 @@ describe MergeRequests::ResolveService do
         }
       end
 
-      let(:service) { MergeRequests::ResolveService.new(project, user, invalid_params) }
+      let(:service) { described_class.new(project, user, invalid_params) }
 
       it 'raises a MissingFiles error' do
         expect { service.execute(merge_request) }.
diff --git a/spec/services/projects/housekeeping_service_spec.rb b/spec/services/projects/housekeeping_service_spec.rb
index eaf63457b32e7e153630aaa85644d162f132636c..fff12beed71eea10e9b2b79ab96b0460ce02ca91 100644
--- a/spec/services/projects/housekeeping_service_spec.rb
+++ b/spec/services/projects/housekeeping_service_spec.rb
@@ -1,7 +1,7 @@
 require 'spec_helper'
 
 describe Projects::HousekeepingService do
-  subject { Projects::HousekeepingService.new(project) }
+  subject { described_class.new(project) }
   let(:project) { create(:project, :repository) }
 
   before do
diff --git a/spec/support/fake_migration_classes.rb b/spec/support/fake_migration_classes.rb
new file mode 100644
index 0000000000000000000000000000000000000000..3de0460c3ca31518700eb591829bbdb74a123e91
--- /dev/null
+++ b/spec/support/fake_migration_classes.rb
@@ -0,0 +1,3 @@
+class FakeRenameReservedPathMigrationV1 < ActiveRecord::Migration
+  include Gitlab::Database::RenameReservedPathsMigration::V1
+end
diff --git a/spec/support/import_export/import_export.yml b/spec/support/import_export/import_export.yml
index 17136dee0006a4546b5aebcdfd276eebe4390217..734d6838f4d46fe5a299abdac80fa50f67680a6b 100644
--- a/spec/support/import_export/import_export.yml
+++ b/spec/support/import_export/import_export.yml
@@ -11,9 +11,6 @@ project_tree:
     - :user
 
 included_attributes:
-  project:
-    - :name
-    - :path
   merge_requests:
     - :id
   user:
@@ -21,4 +18,7 @@ included_attributes:
 
 excluded_attributes:
   merge_requests:
-    - :iid
\ No newline at end of file
+    - :iid
+  project:
+    - :id
+    - :created_at
\ No newline at end of file
diff --git a/spec/support/wait_for_requests.rb b/spec/support/wait_for_requests.rb
index 0bfa7f72ff8ce52eb32a734a2e5abdaffdb1b4df..73da23391ee816843e9069fa4bef9a7ddc5eed44 100644
--- a/spec/support/wait_for_requests.rb
+++ b/spec/support/wait_for_requests.rb
@@ -1,11 +1,15 @@
+require_relative './wait_for_ajax'
+
 module WaitForRequests
   extend self
+  include WaitForAjax
 
   # This is inspired by http://www.salsify.com/blog/engineering/tearing-capybara-ajax-tests
   def wait_for_requests_complete
     Gitlab::Testing::RequestBlockerMiddleware.block_requests!
     wait_for('pending AJAX requests complete') do
-      Gitlab::Testing::RequestBlockerMiddleware.num_active_requests.zero?
+      Gitlab::Testing::RequestBlockerMiddleware.num_active_requests.zero? &&
+        finished_all_ajax_requests?
     end
   ensure
     Gitlab::Testing::RequestBlockerMiddleware.allow_requests!
diff --git a/spec/tasks/config_lint_spec.rb b/spec/tasks/config_lint_spec.rb
index c32f9a740b7baa2e3334277d5a6b1f3eb294e4dd..ed6c5b096635b5ae5bbf0c309ecb64045ff460ea 100644
--- a/spec/tasks/config_lint_spec.rb
+++ b/spec/tasks/config_lint_spec.rb
@@ -5,11 +5,11 @@ describe ConfigLint do
   let(:files){ ['lib/support/fake.sh'] }
 
   it 'errors out if any bash scripts have errors' do
-    expect { ConfigLint.run(files){ system('exit 1') } }.to raise_error(SystemExit)
+    expect { described_class.run(files){ system('exit 1') } }.to raise_error(SystemExit)
   end
 
   it 'passes if all scripts are fine' do
-    expect { ConfigLint.run(files){ system('exit 0') } }.not_to raise_error
+    expect { described_class.run(files){ system('exit 0') } }.not_to raise_error
   end
 end
 
diff --git a/spec/tasks/gitlab/backup_rake_spec.rb b/spec/tasks/gitlab/backup_rake_spec.rb
index 0a4a6ed81454241751a6fdd6fb9fdda88190f6b5..df2f2ce95e653e77f008c2aa9e16cfb55e611d52 100644
--- a/spec/tasks/gitlab/backup_rake_spec.rb
+++ b/spec/tasks/gitlab/backup_rake_spec.rb
@@ -230,11 +230,13 @@ describe 'gitlab:app namespace rake task' do
       before do
         FileUtils.mkdir('tmp/tests/default_storage')
         FileUtils.mkdir('tmp/tests/custom_storage')
+        gitaly_address = Gitlab.config.repositories.storages.default.gitaly_address
         storages = {
-          'default' => { 'path' => Settings.absolute('tmp/tests/default_storage') },
-          'custom' => { 'path' => Settings.absolute('tmp/tests/custom_storage') }
+          'default' => { 'path' => Settings.absolute('tmp/tests/default_storage'), 'gitaly_address' => gitaly_address  },
+          'custom' => { 'path' => Settings.absolute('tmp/tests/custom_storage'), 'gitaly_address' => gitaly_address }
         }
         allow(Gitlab.config.repositories).to receive(:storages).and_return(storages)
+        Gitlab::GitalyClient.configure_channels
 
         # Create the projects now, after mocking the settings but before doing the backup
         project_a
diff --git a/spec/validators/dynamic_path_validator_spec.rb b/spec/validators/dynamic_path_validator_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..b114bfc1bca5a72ff56964e734434b273614acae
--- /dev/null
+++ b/spec/validators/dynamic_path_validator_spec.rb
@@ -0,0 +1,266 @@
+require 'spec_helper'
+
+describe DynamicPathValidator do
+  let(:validator) { described_class.new(attributes: [:path]) }
+
+  # Pass in a full path to remove the format segment:
+  # `/ci/lint(.:format)` -> `/ci/lint`
+  def without_format(path)
+    path.split('(', 2)[0]
+  end
+
+  # Pass in a full path and get the last segment before a wildcard
+  # That's not a parameter
+  # `/*namespace_id/:project_id/builds/artifacts/*ref_name_and_path`
+  #    -> 'builds/artifacts'
+  def path_before_wildcard(path)
+    path = path.gsub(STARTING_WITH_NAMESPACE, "")
+    path_segments = path.split('/').reject(&:empty?)
+    wildcard_index = path_segments.index { |segment| parameter?(segment) }
+
+    segments_before_wildcard = path_segments[0..wildcard_index - 1]
+
+    segments_before_wildcard.join('/')
+  end
+
+  def parameter?(segment)
+    segment =~ /[*:]/
+  end
+
+  # If the path is reserved. Then no conflicting paths can# be created for any
+  # route using this reserved word.
+  #
+  # Both `builds/artifacts` & `build` are covered by reserving the word
+  # `build`
+  def wildcards_include?(path)
+    described_class::WILDCARD_ROUTES.include?(path) ||
+      described_class::WILDCARD_ROUTES.include?(path.split('/').first)
+  end
+
+  def failure_message(missing_words, constant_name, migration_helper)
+    missing_words = Array(missing_words)
+    <<-MSG
+      Found new routes that could cause conflicts with existing namespaced routes
+      for groups or projects.
+
+      Add <#{missing_words.join(', ')}> to `DynamicPathValidator::#{constant_name}
+      to make sure no projects or namespaces can be created with those paths.
+
+      To rename any existing records with those paths you can use the
+      `Gitlab::Database::RenameReservedpathsMigration::<VERSION>.#{migration_helper}`
+      migration helper.
+
+      Make sure to make a note of the renamed records in the release blog post.
+
+    MSG
+  end
+
+  let(:all_routes) do
+    Rails.application.routes.routes.routes.
+      map { |r| r.path.spec.to_s }
+  end
+
+  let(:routes_without_format) { all_routes.map { |path| without_format(path) } }
+
+  # Routes not starting with `/:` or `/*`
+  # all routes not starting with a param
+  let(:routes_not_starting_in_wildcard) { routes_without_format.select { |p| p !~ %r{^/[:*]} } }
+
+  let(:top_level_words) do
+    routes_not_starting_in_wildcard.map do |route|
+      route.split('/')[1]
+    end.compact.uniq
+  end
+
+  # All routes that start with a namespaced path, that have 1 or more
+  # path-segments before having another wildcard parameter.
+  # - Starting with paths:
+  #   - `/*namespace_id/:project_id/`
+  #   - `/*namespace_id/:id/`
+  # - Followed by one or more path-parts not starting with `:` or `*`
+  # - Followed by a path-part that includes a wildcard parameter `*`
+  # At the time of writing these routes match: http://rubular.com/r/Rv2pDE5Dvw
+  STARTING_WITH_NAMESPACE = %r{^/\*namespace_id/:(project_)?id}
+  NON_PARAM_PARTS = %r{[^:*][a-z\-_/]*}
+  ANY_OTHER_PATH_PART = %r{[a-z\-_/:]*}
+  WILDCARD_SEGMENT = %r{\*}
+  let(:namespaced_wildcard_routes) do
+    routes_without_format.select do |p|
+      p =~ %r{#{STARTING_WITH_NAMESPACE}/#{NON_PARAM_PARTS}/#{ANY_OTHER_PATH_PART}#{WILDCARD_SEGMENT}}
+    end
+  end
+
+  # This will return all paths that are used in a namespaced route
+  # before another wildcard path:
+  #
+  # /*namespace_id/:project_id/builds/artifacts/*ref_name_and_path
+  # /*namespace_id/:project_id/info/lfs/objects/*oid
+  # /*namespace_id/:project_id/commits/*id
+  # /*namespace_id/:project_id/builds/:build_id/artifacts/file/*path
+  # -> ['builds/artifacts', 'info/lfs/objects', 'commits', 'artifacts/file']
+  let(:all_wildcard_paths) do
+    namespaced_wildcard_routes.map do |route|
+      path_before_wildcard(route)
+    end.uniq
+  end
+
+  STARTING_WITH_GROUP = %r{^/groups/\*(group_)?id/}
+  let(:group_routes) do
+    routes_without_format.select do |path|
+      path =~ STARTING_WITH_GROUP
+    end
+  end
+
+  let(:paths_after_group_id) do
+    group_routes.map do |route|
+      route.gsub(STARTING_WITH_GROUP, '').split('/').first
+    end.uniq
+  end
+
+  describe 'TOP_LEVEL_ROUTES' do
+    it 'includes all the top level namespaces' do
+      failure_block = lambda do
+        missing_words = top_level_words - described_class::TOP_LEVEL_ROUTES
+        failure_message(missing_words, 'TOP_LEVEL_ROUTES', 'rename_root_paths')
+      end
+
+      expect(described_class::TOP_LEVEL_ROUTES)
+        .to include(*top_level_words), failure_block
+    end
+  end
+
+  describe 'GROUP_ROUTES' do
+    it "don't contain a second wildcard" do
+      failure_block = lambda do
+        missing_words = paths_after_group_id - described_class::GROUP_ROUTES
+        failure_message(missing_words, 'GROUP_ROUTES', 'rename_child_paths')
+      end
+
+      expect(described_class::GROUP_ROUTES)
+        .to include(*paths_after_group_id), failure_block
+    end
+  end
+
+  describe 'WILDCARD_ROUTES' do
+    it 'includes all paths that can be used after a namespace/project path' do
+      aggregate_failures do
+        all_wildcard_paths.each do |path|
+          expect(wildcards_include?(path))
+            .to be(true), failure_message(path, 'WILDCARD_ROUTES', 'rename_wildcard_paths')
+        end
+      end
+    end
+  end
+
+  describe '.without_reserved_wildcard_paths_regex' do
+    subject { described_class.without_reserved_wildcard_paths_regex }
+
+    it 'rejects paths starting with a reserved top level' do
+      expect(subject).not_to match('dashboard/hello/world')
+      expect(subject).not_to match('dashboard')
+    end
+
+    it 'matches valid paths with a toplevel word in a different place' do
+      expect(subject).to match('parent/dashboard/project-path')
+    end
+
+    it 'rejects paths containing a wildcard reserved word' do
+      expect(subject).not_to match('hello/edit')
+      expect(subject).not_to match('hello/edit/in-the-middle')
+      expect(subject).not_to match('foo/bar1/refs/master/logs_tree')
+    end
+
+    it 'matches valid paths' do
+      expect(subject).to match('parent/child/project-path')
+    end
+  end
+
+  describe '.regex_excluding_child_paths' do
+    let(:subject) { described_class.without_reserved_child_paths_regex }
+
+    it 'rejects paths containing a child reserved word' do
+      expect(subject).not_to match('hello/group_members')
+      expect(subject).not_to match('hello/activity/in-the-middle')
+      expect(subject).not_to match('foo/bar1/refs/master/logs_tree')
+    end
+
+    it 'allows a child path on the top level' do
+      expect(subject).to match('activity/foo')
+      expect(subject).to match('avatar')
+    end
+  end
+
+  describe ".valid?" do
+    it 'is not case sensitive' do
+      expect(described_class.valid?("Users")).to be_falsey
+    end
+
+    it "isn't valid when the top level is reserved" do
+      test_path = 'u/should-be-a/reserved-word'
+
+      expect(described_class.valid?(test_path)).to be_falsey
+    end
+
+    it "isn't valid if any of the path segments is reserved" do
+      test_path = 'the-wildcard/wikis/is-not-allowed'
+
+      expect(described_class.valid?(test_path)).to be_falsey
+    end
+
+    it "is valid if the path doesn't contain reserved words" do
+      test_path = 'there-are/no-wildcards/in-this-path'
+
+      expect(described_class.valid?(test_path)).to be_truthy
+    end
+
+    it 'allows allows a child path on the last spot' do
+      test_path = 'there/can-be-a/project-called/labels'
+
+      expect(described_class.valid?(test_path)).to be_truthy
+    end
+
+    it 'rejects a child path somewhere else' do
+      test_path = 'there/can-be-no/labels/group'
+
+      expect(described_class.valid?(test_path)).to be_falsey
+    end
+
+    it 'rejects paths that are in an incorrect format' do
+      test_path = 'incorrect/format.git'
+
+      expect(described_class.valid?(test_path)).to be_falsey
+    end
+  end
+
+  describe '#path_reserved_for_record?' do
+    it 'reserves a sub-group named activity' do
+      group = build(:group, :nested, path: 'activity')
+
+      expect(validator.path_reserved_for_record?(group, 'activity')).to be_truthy
+    end
+
+    it "doesn't reserve a project called activity" do
+      project = build(:project, path: 'activity')
+
+      expect(validator.path_reserved_for_record?(project, 'activity')).to be_falsey
+    end
+  end
+
+  describe '#validates_each' do
+    it 'adds a message when the path is not in the correct format' do
+      group = build(:group)
+
+      validator.validate_each(group, :path, "Path with spaces, and comma's!")
+
+      expect(group.errors[:path]).to include(Gitlab::Regex.namespace_regex_message)
+    end
+
+    it 'adds a message when the path is not in the correct format' do
+      group = build(:group, path: 'users')
+
+      validator.validate_each(group, :path, 'users')
+
+      expect(group.errors[:path]).to include('users is a reserved name')
+    end
+  end
+end
diff --git a/spec/workers/delete_user_worker_spec.rb b/spec/workers/delete_user_worker_spec.rb
index 0765573408cf3cc403e77d234dba3f89703f7754..5912dd76262fbd9d954763d3068cd5c182214fc4 100644
--- a/spec/workers/delete_user_worker_spec.rb
+++ b/spec/workers/delete_user_worker_spec.rb
@@ -8,13 +8,13 @@ describe DeleteUserWorker do
     expect_any_instance_of(Users::DestroyService).to receive(:execute).
                                                       with(user, {})
 
-    DeleteUserWorker.new.perform(current_user.id, user.id)
+    described_class.new.perform(current_user.id, user.id)
   end
 
   it "uses symbolized keys" do
     expect_any_instance_of(Users::DestroyService).to receive(:execute).
                                                       with(user, test: "test")
 
-    DeleteUserWorker.new.perform(current_user.id, user.id, "test" => "test")
+    described_class.new.perform(current_user.id, user.id, "test" => "test")
   end
 end
diff --git a/spec/workers/emails_on_push_worker_spec.rb b/spec/workers/emails_on_push_worker_spec.rb
index 8cf2b888f9a2fdf05da041a05c57c081d98c36e4..a0ed85cc0b37fdfec1acfaf429a926a291cc0c47 100644
--- a/spec/workers/emails_on_push_worker_spec.rb
+++ b/spec/workers/emails_on_push_worker_spec.rb
@@ -12,7 +12,7 @@ describe EmailsOnPushWorker do
   let(:perform) { subject.perform(project.id, recipients, data.stringify_keys) }
   let(:email) { ActionMailer::Base.deliveries.last }
 
-  subject { EmailsOnPushWorker.new }
+  subject { described_class.new }
 
   describe "#perform" do
     context "when push is a new branch" do
diff --git a/spec/workers/git_garbage_collect_worker_spec.rb b/spec/workers/git_garbage_collect_worker_spec.rb
index 029f35512e07ad5651900b390951e74fbfb75bf9..7a590f64e3c602fd89c0213cb1605680c6b37c78 100644
--- a/spec/workers/git_garbage_collect_worker_spec.rb
+++ b/spec/workers/git_garbage_collect_worker_spec.rb
@@ -6,7 +6,7 @@ describe GitGarbageCollectWorker do
   let(:project) { create(:project, :repository) }
   let(:shell) { Gitlab::Shell.new }
 
-  subject { GitGarbageCollectWorker.new }
+  subject { described_class.new }
 
   describe "#perform" do
     it "flushes ref caches when the task is 'gc'" do
diff --git a/spec/workers/gitlab_usage_ping_worker_spec.rb b/spec/workers/gitlab_usage_ping_worker_spec.rb
index b6c080f36f4769a8bfb93148ec681bb3e76df189..262410445337bc4c35da638b4f5dcadd6be4265d 100644
--- a/spec/workers/gitlab_usage_ping_worker_spec.rb
+++ b/spec/workers/gitlab_usage_ping_worker_spec.rb
@@ -1,7 +1,7 @@
 require 'spec_helper'
 
 describe GitlabUsagePingWorker do
-  subject { GitlabUsagePingWorker.new }
+  subject { described_class.new }
 
   it "sends POST request" do
     stub_application_setting(usage_ping_enabled: true)
diff --git a/spec/workers/group_destroy_worker_spec.rb b/spec/workers/group_destroy_worker_spec.rb
index 1ff5a3b903438ec7cb21f96c54be564e6c100508..c78efc67076f6a62c862e51021a841d93fc0358f 100644
--- a/spec/workers/group_destroy_worker_spec.rb
+++ b/spec/workers/group_destroy_worker_spec.rb
@@ -5,7 +5,7 @@ describe GroupDestroyWorker do
   let(:user) { create(:admin) }
   let!(:project) { create(:empty_project, namespace: group) }
 
-  subject { GroupDestroyWorker.new }
+  subject { described_class.new }
 
   describe "#perform" do
     it "deletes the project" do
diff --git a/spec/workers/merge_worker_spec.rb b/spec/workers/merge_worker_spec.rb
index b5e1fdb8ded40ae24e14226c5bb107d6daa0e10a..303193bab9bce2aa6d0f90726186a63c7eb59808 100644
--- a/spec/workers/merge_worker_spec.rb
+++ b/spec/workers/merge_worker_spec.rb
@@ -15,7 +15,7 @@ describe MergeWorker do
     it 'clears cache of source repo after removing source branch' do
       expect(source_project.repository.branch_names).to include('markdown')
 
-      MergeWorker.new.perform(
+      described_class.new.perform(
         merge_request.id, merge_request.author_id,
         commit_message: 'wow such merge',
         should_remove_source_branch: true)
diff --git a/spec/workers/post_receive_spec.rb b/spec/workers/post_receive_spec.rb
index a2a559a2369a7545a60562d1ec4dd00a24c6650a..5ab3c4a0e341bfb1a78888fd04a4d11e189503c7 100644
--- a/spec/workers/post_receive_spec.rb
+++ b/spec/workers/post_receive_spec.rb
@@ -10,7 +10,7 @@ describe PostReceive do
 
   context "as a resque worker" do
     it "reponds to #perform" do
-      expect(PostReceive.new).to respond_to(:perform)
+      expect(described_class.new).to respond_to(:perform)
     end
   end
 
@@ -25,7 +25,7 @@ describe PostReceive do
       it "calls GitTagPushService" do
         expect_any_instance_of(GitPushService).to receive(:execute).and_return(true)
         expect_any_instance_of(GitTagPushService).not_to receive(:execute)
-        PostReceive.new.perform(pwd(project), key_id, base64_changes)
+        described_class.new.perform(pwd(project), key_id, base64_changes)
       end
     end
 
@@ -35,7 +35,7 @@ describe PostReceive do
       it "calls GitTagPushService" do
         expect_any_instance_of(GitPushService).not_to receive(:execute)
         expect_any_instance_of(GitTagPushService).to receive(:execute).and_return(true)
-        PostReceive.new.perform(pwd(project), key_id, base64_changes)
+        described_class.new.perform(pwd(project), key_id, base64_changes)
       end
     end
 
@@ -45,12 +45,12 @@ describe PostReceive do
       it "does not call any of the services" do
         expect_any_instance_of(GitPushService).not_to receive(:execute)
         expect_any_instance_of(GitTagPushService).not_to receive(:execute)
-        PostReceive.new.perform(pwd(project), key_id, base64_changes)
+        described_class.new.perform(pwd(project), key_id, base64_changes)
       end
     end
 
     context "gitlab-ci.yml" do
-      subject { PostReceive.new.perform(pwd(project), key_id, base64_changes) }
+      subject { described_class.new.perform(pwd(project), key_id, base64_changes) }
 
       context "creates a Ci::Pipeline for every change" do
         before do
@@ -75,7 +75,7 @@ describe PostReceive do
   context "webhook" do
     it "fetches the correct project" do
       expect(Project).to receive(:find_by_full_path).with(project.path_with_namespace).and_return(project)
-      PostReceive.new.perform(pwd(project), key_id, base64_changes)
+      described_class.new.perform(pwd(project), key_id, base64_changes)
     end
 
     it "does not run if the author is not in the project" do
@@ -85,7 +85,7 @@ describe PostReceive do
 
       expect(project).not_to receive(:execute_hooks)
 
-      expect(PostReceive.new.perform(pwd(project), key_id, base64_changes)).to be_falsey
+      expect(described_class.new.perform(pwd(project), key_id, base64_changes)).to be_falsey
     end
 
     it "asks the project to trigger all hooks" do
@@ -93,14 +93,14 @@ describe PostReceive do
       expect(project).to receive(:execute_hooks).twice
       expect(project).to receive(:execute_services).twice
 
-      PostReceive.new.perform(pwd(project), key_id, base64_changes)
+      described_class.new.perform(pwd(project), key_id, base64_changes)
     end
 
     it "enqueues a UpdateMergeRequestsWorker job" do
       allow(Project).to receive(:find_by_full_path).and_return(project)
       expect(UpdateMergeRequestsWorker).to receive(:perform_async).with(project.id, project.owner.id, any_args)
 
-      PostReceive.new.perform(pwd(project), key_id, base64_changes)
+      described_class.new.perform(pwd(project), key_id, base64_changes)
     end
   end
 
diff --git a/spec/workers/project_destroy_worker_spec.rb b/spec/workers/project_destroy_worker_spec.rb
index 0ab42f995103df12f31caba24f488823f7416a77..3d135f40c1fcd9a17f8994fe4c3aadc522f0f4bb 100644
--- a/spec/workers/project_destroy_worker_spec.rb
+++ b/spec/workers/project_destroy_worker_spec.rb
@@ -4,7 +4,7 @@ describe ProjectDestroyWorker do
   let(:project) { create(:project, :repository) }
   let(:path) { project.repository.path_to_repo }
 
-  subject { ProjectDestroyWorker.new }
+  subject { described_class.new }
 
   describe "#perform" do
     it "deletes the project" do
diff --git a/spec/workers/remove_expired_members_worker_spec.rb b/spec/workers/remove_expired_members_worker_spec.rb
index 402aa1e714e44557a7a1f44266a70eb9d95280c2..058fdf4c0094752e6e988d9f2e2356de7637a7a7 100644
--- a/spec/workers/remove_expired_members_worker_spec.rb
+++ b/spec/workers/remove_expired_members_worker_spec.rb
@@ -1,7 +1,7 @@
 require 'spec_helper'
 
 describe RemoveExpiredMembersWorker do
-  let(:worker) { RemoveExpiredMembersWorker.new }
+  let(:worker) { described_class.new }
 
   describe '#perform' do
     context 'project members' do
diff --git a/spec/workers/remove_unreferenced_lfs_objects_worker_spec.rb b/spec/workers/remove_unreferenced_lfs_objects_worker_spec.rb
index 6d42946de3875599d0e95431570cb472cd294a24..1c183ce54f4409ed27b6deef50e9645414b371da 100644
--- a/spec/workers/remove_unreferenced_lfs_objects_worker_spec.rb
+++ b/spec/workers/remove_unreferenced_lfs_objects_worker_spec.rb
@@ -1,7 +1,7 @@
 require 'spec_helper'
 
 describe RemoveUnreferencedLfsObjectsWorker do
-  let(:worker) { RemoveUnreferencedLfsObjectsWorker.new }
+  let(:worker) { described_class.new }
 
   describe '#perform' do
     let!(:unreferenced_lfs_object1) { create(:lfs_object, oid: '1') }
diff --git a/spec/workers/repository_fork_worker_spec.rb b/spec/workers/repository_fork_worker_spec.rb
index 7d6a2db29720386b5e67d8bc3697bc1a3aff1295..5e1cb74c7fcf75ef6524e647b8ac45a426a46abb 100644
--- a/spec/workers/repository_fork_worker_spec.rb
+++ b/spec/workers/repository_fork_worker_spec.rb
@@ -5,7 +5,7 @@ describe RepositoryForkWorker do
   let(:fork_project) { create(:project, :repository, forked_from_project: project) }
   let(:shell) { Gitlab::Shell.new }
 
-  subject { RepositoryForkWorker.new }
+  subject { described_class.new }
 
   before do
     allow(subject).to receive(:gitlab_shell).and_return(shell)