diff --git a/.eslintrc b/.eslintrc
index 73cd7ecf66d7261830d430f6dc626e6a2fb835a2..c72a5e0335bda10b9b1576c23a7171b64c5fde5b 100644
--- a/.eslintrc
+++ b/.eslintrc
@@ -11,6 +11,7 @@
     "gon": false,
     "localStorage": false
   },
+  "parser": "babel-eslint",
   "plugins": [
     "filenames",
     "import",
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 790d9a1f72a33b10600141ffb621af03fde595bd..a3ce1de50c2739aa9d6bb692a2dd3ac5b42c520a 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -63,7 +63,7 @@ stages:
 .only-master-and-ee-or-mysql: &only-master-and-ee-or-mysql
   only:
     - /mysql/
-    - /-stable$/
+    - /-stable/
     - master@gitlab-org/gitlab-ce
     - master@gitlab/gitlabhq
     - tags@gitlab-org/gitlab-ce
@@ -474,9 +474,8 @@ codeclimate:
   services:
     - docker:dind
   script:
-    - docker pull codeclimate/codeclimate
-    - docker run --env CODECLIMATE_CODE="$PWD" --volume "$PWD":/code --volume /var/run/docker.sock:/var/run/docker.sock --volume /tmp/cc:/tmp/cc codeclimate/codeclimate analyze -f json > codeclimate.json
-    - sed -i.bak 's/\({"body":"\)[^"]*\("}\)/\1\2/g' codeclimate.json
+    - docker run --env CODECLIMATE_CODE="$PWD" --volume "$PWD":/code --volume /var/run/docker.sock:/var/run/docker.sock --volume /tmp/cc:/tmp/cc codeclimate/codeclimate analyze -f json > raw_codeclimate.json
+    - cat raw_codeclimate.json | docker run -i stedolan/jq -c 'map({check_name,fingerprint,location})' > codeclimate.json
   artifacts:
     paths: [codeclimate.json]
 
diff --git a/CHANGELOG.md b/CHANGELOG.md
index f372cbf91e8d9238708afdb26e98cfbb7d9bb2fc..7591559da22a8584e3c56ab60e435051b3e8f6d8 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,15 @@
 documentation](doc/development/changelog.md) for instructions on adding your own
 entry.
 
+## 9.3.3 (2017-06-30)
+
+- Fix head pipeline stored in merge request for external pipelines. !12478
+- Bring back branches badge to main project page. !12548
+- Fix diff of requirements.txt file by not matching newlines as part of package names.
+- Perform housekeeping only when an import of a fresh project is completed.
+- Fixed issue boards closed list not showing all closed issues.
+- Fixed multi-line markdown tooltip buttons in issue edit form.
+
 ## 9.3.2 (2017-06-27)
 
 - API: Fix optional arugments for POST :id/variables. !12474
diff --git a/GITALY_SERVER_VERSION b/GITALY_SERVER_VERSION
index 54d1a4f2a4a7f6afc19897c88a7b73c17ccc54fb..a803cc227fe6ff1fbb6dcfc2dde3e4ccc450257e 100644
--- a/GITALY_SERVER_VERSION
+++ b/GITALY_SERVER_VERSION
@@ -1 +1 @@
-0.13.0
+0.14.0
diff --git a/app/assets/javascripts/awards_handler.js b/app/assets/javascripts/awards_handler.js
index c34d80f06019901a7fbb914b0139ee572112eb1b..18cd04b176a43b352473140d763688e5a10f1904 100644
--- a/app/assets/javascripts/awards_handler.js
+++ b/app/assets/javascripts/awards_handler.js
@@ -2,7 +2,6 @@
 /* global Flash */
 
 import Cookies from 'js-cookie';
-import * as Emoji from './emoji';
 
 const animationEndEventString = 'animationend webkitAnimationEnd MSAnimationEnd oAnimationEnd';
 const transitionEndEventString = 'transitionend webkitTransitionEnd oTransitionEnd MSTransitionEnd';
@@ -24,27 +23,9 @@ const categoryLabelMap = {
   flags: 'Flags',
 };
 
-function renderCategory(name, emojiList, opts = {}) {
-  return `
-    <h5 class="emoji-menu-title">
-      ${name}
-    </h5>
-    <ul class="clearfix emoji-menu-list ${opts.menuListClass || ''}">
-      ${emojiList.map(emojiName => `
-        <li class="emoji-menu-list-item">
-          <button class="emoji-menu-btn text-center js-emoji-btn" type="button">
-            ${Emoji.glEmojiTag(emojiName, {
-              sprite: true,
-            })}
-          </button>
-        </li>
-      `).join('\n')}
-    </ul>
-  `;
-}
-
-export default class AwardsHandler {
-  constructor() {
+class AwardsHandler {
+  constructor(emoji) {
+    this.emoji = emoji;
     this.eventListeners = [];
     // If the user shows intent let's pre-build the menu
     this.registerEventListener('one', $(document), 'mouseenter focus', '.js-add-award', 'mouseenter focus', () => {
@@ -78,10 +59,10 @@ export default class AwardsHandler {
       const $target = $(e.currentTarget);
       const $glEmojiElement = $target.find('gl-emoji');
       const $spriteIconElement = $target.find('.icon');
-      const emoji = ($glEmojiElement.length ? $glEmojiElement : $spriteIconElement).data('name');
+      const emojiName = ($glEmojiElement.length ? $glEmojiElement : $spriteIconElement).data('name');
 
       $target.closest('.js-awards-block').addClass('current');
-      this.addAward(this.getVotesBlock(), this.getAwardUrl(), emoji);
+      this.addAward(this.getVotesBlock(), this.getAwardUrl(), emojiName);
     });
   }
 
@@ -139,16 +120,16 @@ export default class AwardsHandler {
     this.isCreatingEmojiMenu = true;
 
     // Render the first category
-    const categoryMap = Emoji.getEmojiCategoryMap();
+    const categoryMap = this.emoji.getEmojiCategoryMap();
     const categoryNameKey = Object.keys(categoryMap)[0];
     const emojisInCategory = categoryMap[categoryNameKey];
-    const firstCategory = renderCategory(categoryLabelMap[categoryNameKey], emojisInCategory);
+    const firstCategory = this.renderCategory(categoryLabelMap[categoryNameKey], emojisInCategory);
 
     // Render the frequently used
     const frequentlyUsedEmojis = this.getFrequentlyUsedEmojis();
     let frequentlyUsedCatgegory = '';
     if (frequentlyUsedEmojis.length > 0) {
-      frequentlyUsedCatgegory = renderCategory('Frequently used', frequentlyUsedEmojis, {
+      frequentlyUsedCatgegory = this.renderCategory('Frequently used', frequentlyUsedEmojis, {
         menuListClass: 'frequent-emojis',
       });
     }
@@ -179,7 +160,7 @@ export default class AwardsHandler {
     }
     this.isAddingRemainingEmojiMenuCategories = true;
 
-    const categoryMap = Emoji.getEmojiCategoryMap();
+    const categoryMap = this.emoji.getEmojiCategoryMap();
 
     // Avoid the jank and render the remaining categories separately
     // This will take more time, but makes UI more responsive
@@ -191,7 +172,7 @@ export default class AwardsHandler {
         promiseChain.then(() =>
           new Promise((resolve) => {
             const emojisInCategory = categoryMap[categoryNameKey];
-            const categoryMarkup = renderCategory(
+            const categoryMarkup = this.renderCategory(
               categoryLabelMap[categoryNameKey],
               emojisInCategory,
             );
@@ -216,6 +197,25 @@ export default class AwardsHandler {
     });
   }
 
+  renderCategory(name, emojiList, opts = {}) {
+    return `
+      <h5 class="emoji-menu-title">
+        ${name}
+      </h5>
+      <ul class="clearfix emoji-menu-list ${opts.menuListClass || ''}">
+        ${emojiList.map(emojiName => `
+          <li class="emoji-menu-list-item">
+            <button class="emoji-menu-btn text-center js-emoji-btn" type="button">
+              ${this.emoji.glEmojiTag(emojiName, {
+                sprite: true,
+              })}
+            </button>
+          </li>
+        `).join('\n')}
+      </ul>
+    `;
+  }
+
   positionMenu($menu, $addBtn) {
     const position = $addBtn.data('position');
     // The menu could potentially be off-screen or in a hidden overflow element
@@ -234,7 +234,7 @@ export default class AwardsHandler {
   }
 
   addAward(votesBlock, awardUrl, emoji, checkMutuality, callback) {
-    const normalizedEmoji = Emoji.normalizeEmojiName(emoji);
+    const normalizedEmoji = this.emoji.normalizeEmojiName(emoji);
     const $emojiButton = this.findEmojiIcon(votesBlock, normalizedEmoji).parent();
     this.postEmoji($emojiButton, awardUrl, normalizedEmoji, () => {
       this.addAwardToEmojiBar(votesBlock, normalizedEmoji, checkMutuality);
@@ -249,7 +249,7 @@ export default class AwardsHandler {
       this.checkMutuality(votesBlock, emoji);
     }
     this.addEmojiToFrequentlyUsedList(emoji);
-    const normalizedEmoji = Emoji.normalizeEmojiName(emoji);
+    const normalizedEmoji = this.emoji.normalizeEmojiName(emoji);
     const $emojiButton = this.findEmojiIcon(votesBlock, normalizedEmoji).parent();
     if ($emojiButton.length > 0) {
       if (this.isActive($emojiButton)) {
@@ -374,7 +374,7 @@ export default class AwardsHandler {
   createAwardButtonForVotesBlock(votesBlock, emojiName) {
     const buttonHtml = `
       <button class="btn award-control js-emoji-btn has-tooltip active" title="You" data-placement="bottom">
-        ${Emoji.glEmojiTag(emojiName)}
+        ${this.emoji.glEmojiTag(emojiName)}
         <span class="award-control-text js-counter">1</span>
       </button>
     `;
@@ -440,7 +440,7 @@ export default class AwardsHandler {
   }
 
   addEmojiToFrequentlyUsedList(emoji) {
-    if (Emoji.isEmojiNameValid(emoji)) {
+    if (this.emoji.isEmojiNameValid(emoji)) {
       this.frequentlyUsedEmojis = _.uniq(this.getFrequentlyUsedEmojis().concat(emoji));
       Cookies.set('frequently_used_emojis', this.frequentlyUsedEmojis.join(','), { expires: 365 });
     }
@@ -450,7 +450,7 @@ export default class AwardsHandler {
     return this.frequentlyUsedEmojis || (() => {
       const frequentlyUsedEmojis = _.uniq((Cookies.get('frequently_used_emojis') || '').split(','));
       this.frequentlyUsedEmojis = frequentlyUsedEmojis.filter(
-        inputName => Emoji.isEmojiNameValid(inputName),
+        inputName => this.emoji.isEmojiNameValid(inputName),
       );
 
       return this.frequentlyUsedEmojis;
@@ -493,7 +493,7 @@ export default class AwardsHandler {
   }
 
   findMatchingEmojiElements(query) {
-    const emojiMatches = Emoji.filterEmojiNamesByAlias(query);
+    const emojiMatches = this.emoji.filterEmojiNamesByAlias(query);
     const $emojiElements = $('.emoji-menu-list:not(.frequent-emojis) [data-name]');
     const $matchingElements = $emojiElements
       .filter((i, elm) => emojiMatches.indexOf(elm.dataset.name) >= 0);
@@ -507,3 +507,12 @@ export default class AwardsHandler {
     $('.emoji-menu').remove();
   }
 }
+
+let awardsHandlerPromise = null;
+export default function loadAwardsHandler(reload = false) {
+  if (!awardsHandlerPromise || reload) {
+    awardsHandlerPromise = import(/* webpackChunkName: 'emoji' */ './emoji')
+      .then(Emoji => new AwardsHandler(Emoji));
+  }
+  return awardsHandlerPromise;
+}
diff --git a/app/assets/javascripts/behaviors/gl_emoji.js b/app/assets/javascripts/behaviors/gl_emoji.js
index 8156e491a4245b38556f7cb8b8e325f8205653b8..7e98e04303a427d9a34ec27eea2a4d505ad8222a 100644
--- a/app/assets/javascripts/behaviors/gl_emoji.js
+++ b/app/assets/javascripts/behaviors/gl_emoji.js
@@ -1,5 +1,4 @@
 import installCustomElements from 'document-register-element';
-import { emojiImageTag, emojiFallbackImageSrc } from '../emoji';
 import isEmojiUnicodeSupported from '../emoji/support';
 
 installCustomElements(window);
@@ -32,11 +31,19 @@ export default function installGlEmojiElement() {
         // IE 11 doesn't like adding multiple at once :(
         this.classList.add('emoji-icon');
         this.classList.add(fallbackSpriteClass);
-      } else if (hasImageFallback) {
-        this.innerHTML = emojiImageTag(name, fallbackSrc);
       } else {
-        const src = emojiFallbackImageSrc(name);
-        this.innerHTML = emojiImageTag(name, src);
+        import(/* webpackChunkName: 'emoji' */ '../emoji')
+          .then(({ emojiImageTag, emojiFallbackImageSrc }) => {
+            if (hasImageFallback) {
+              this.innerHTML = emojiImageTag(name, fallbackSrc);
+            } else {
+              const src = emojiFallbackImageSrc(name);
+              this.innerHTML = emojiImageTag(name, src);
+            }
+          })
+          .catch(() => {
+            // do nothing
+          });
       }
     }
   };
diff --git a/app/assets/javascripts/boards/components/board_sidebar.js b/app/assets/javascripts/boards/components/board_sidebar.js
index c7afd4ead6bf6e7ae1fd73de48cf5af5c11f412c..590b7be36e399fc58dc91fb8cef3984b3eeda99b 100644
--- a/app/assets/javascripts/boards/components/board_sidebar.js
+++ b/app/assets/javascripts/boards/components/board_sidebar.js
@@ -34,7 +34,10 @@ gl.issueBoards.BoardSidebar = Vue.extend({
     },
     milestoneTitle() {
       return this.issue.milestone ? this.issue.milestone.title : 'No Milestone';
-    }
+    },
+    canRemove() {
+      return !this.list.preset;
+    },
   },
   watch: {
     detail: {
diff --git a/app/assets/javascripts/boards/components/sidebar/remove_issue.js b/app/assets/javascripts/boards/components/sidebar/remove_issue.js
index 5597f128b80db4aa078042afd11cf60820d8d4e9..6a900d4abd0ce2d323abe3eb2dc380bcf500aa6c 100644
--- a/app/assets/javascripts/boards/components/sidebar/remove_issue.js
+++ b/app/assets/javascripts/boards/components/sidebar/remove_issue.js
@@ -46,8 +46,7 @@ gl.issueBoards.RemoveIssueBtn = Vue.extend({
   },
   template: `
     <div
-      class="block list"
-      v-if="list.type !== 'closed'">
+      class="block list">
       <button
         class="btn btn-default btn-block"
         type="button"
diff --git a/app/assets/javascripts/build.js b/app/assets/javascripts/build.js
index 9974e135022554e884c108cf986074a7827c5bae..60103155ce040c4592c3ec32e0431792a9542058 100644
--- a/app/assets/javascripts/build.js
+++ b/app/assets/javascripts/build.js
@@ -85,9 +85,8 @@ window.Build = (function () {
         if (!this.hasBeenScrolled) {
           this.scrollToBottom();
         }
-      });
-
-    this.verifyTopPosition();
+      })
+      .then(() => this.verifyTopPosition());
   }
 
   Build.prototype.canScroll = function () {
@@ -176,7 +175,7 @@ window.Build = (function () {
     }
 
     if ($flashError.length) {
-      topPostion += $flashError.outerHeight();
+      topPostion += $flashError.outerHeight() + prependTopDefault;
     }
 
     this.$buildTrace.css({
@@ -234,7 +233,8 @@ window.Build = (function () {
                 if (!this.hasBeenScrolled) {
                   this.scrollToBottom();
                 }
-              });
+              })
+              .then(() => this.verifyTopPosition());
           }, 4000);
         } else {
           this.$buildRefreshAnimation.remove();
diff --git a/app/assets/javascripts/diff.js b/app/assets/javascripts/diff.js
index 725ec7b9c70aa089ff3090958674625ea6fefa18..1be9df19c816827230e951b57a0afa1211595db7 100644
--- a/app/assets/javascripts/diff.js
+++ b/app/assets/javascripts/diff.js
@@ -1,6 +1,7 @@
 /* eslint-disable class-methods-use-this */
 
 import './lib/utils/url_utility';
+import FilesCommentButton from './files_comment_button';
 
 const UNFOLD_COUNT = 20;
 let isBound = false;
@@ -8,8 +9,10 @@ let isBound = false;
 class Diff {
   constructor() {
     const $diffFile = $('.files .diff-file');
+
     $diffFile.singleFileDiff();
-    $diffFile.filesCommentButton();
+
+    FilesCommentButton.init($diffFile);
 
     $diffFile.each((index, file) => new gl.ImageFile(file));
 
diff --git a/app/assets/javascripts/diff_notes/components/diff_note_avatars.js b/app/assets/javascripts/diff_notes/components/diff_note_avatars.js
index 517bdb6be092a9acdc20dfe8878d7877748ca490..c37249c060a69bf1b51b8d15db2cb57fdcd4840f 100644
--- a/app/assets/javascripts/diff_notes/components/diff_note_avatars.js
+++ b/app/assets/javascripts/diff_notes/components/diff_note_avatars.js
@@ -139,9 +139,9 @@ const DiffNoteAvatars = Vue.extend({
       const notesCount = this.notesCount;
 
       $(this.$el).closest('.js-avatar-container')
-        .toggleClass('js-no-comment-btn', notesCount > 0)
+        .toggleClass('no-comment-btn', notesCount > 0)
         .nextUntil('.js-avatar-container')
-        .toggleClass('js-no-comment-btn', notesCount > 0);
+        .toggleClass('no-comment-btn', notesCount > 0);
     },
     toggleDiscussionsToggleState() {
       const $notesHolders = $(this.$el).closest('.code').find('.notes_holder');
diff --git a/app/assets/javascripts/files_comment_button.js b/app/assets/javascripts/files_comment_button.js
index 534e651b030b303056cdfa5449f957bbf922a891..d02e4cd5876a1a2778b01b459d064a66ef670283 100644
--- a/app/assets/javascripts/files_comment_button.js
+++ b/app/assets/javascripts/files_comment_button.js
@@ -1,150 +1,73 @@
 /* eslint-disable func-names, space-before-function-paren, no-var, prefer-rest-params, wrap-iife, max-len, one-var, one-var-declaration-per-line, quotes, prefer-template, newline-per-chained-call, comma-dangle, new-cap, no-else-return, consistent-return */
-/* global FilesCommentButton */
 /* global notes */
 
-let $commentButtonTemplate;
-
-window.FilesCommentButton = (function() {
-  var COMMENT_BUTTON_CLASS, EMPTY_CELL_CLASS, LINE_COLUMN_CLASSES, LINE_CONTENT_CLASS, LINE_HOLDER_CLASS, LINE_NUMBER_CLASS, OLD_LINE_CLASS, TEXT_FILE_SELECTOR, UNFOLDABLE_LINE_CLASS;
-
-  COMMENT_BUTTON_CLASS = '.add-diff-note';
-
-  LINE_HOLDER_CLASS = '.line_holder';
-
-  LINE_NUMBER_CLASS = 'diff-line-num';
-
-  LINE_CONTENT_CLASS = 'line_content';
-
-  UNFOLDABLE_LINE_CLASS = 'js-unfold';
-
-  EMPTY_CELL_CLASS = 'empty-cell';
-
-  OLD_LINE_CLASS = 'old_line';
-
-  LINE_COLUMN_CLASSES = "." + LINE_NUMBER_CLASS + ", .line_content";
-
-  TEXT_FILE_SELECTOR = '.text-file';
-
-  function FilesCommentButton(filesContainerElement) {
-    this.render = this.render.bind(this);
-    this.hideButton = this.hideButton.bind(this);
-    this.isParallelView = notes.isParallelView();
-    filesContainerElement.on('mouseover', LINE_COLUMN_CLASSES, this.render)
-      .on('mouseleave', LINE_COLUMN_CLASSES, this.hideButton);
-  }
-
-  FilesCommentButton.prototype.render = function(e) {
-    var $currentTarget, buttonParentElement, lineContentElement, textFileElement, $button;
-    $currentTarget = $(e.currentTarget);
-
-    if ($currentTarget.hasClass('js-no-comment-btn')) return;
-
-    lineContentElement = this.getLineContent($currentTarget);
-    buttonParentElement = this.getButtonParent($currentTarget);
-
-    if (!this.validateButtonParent(buttonParentElement) || !this.validateLineContent(lineContentElement)) return;
-
-    $button = $(COMMENT_BUTTON_CLASS, buttonParentElement);
-    buttonParentElement.addClass('is-over')
-      .nextUntil(`.${LINE_CONTENT_CLASS}`).addClass('is-over');
-
-    if ($button.length) {
-      return;
+/* Developer beware! Do not add logic to showButton or hideButton
+ * that will force a reflow. Doing so will create a signficant performance
+ * bottleneck for pages with large diffs. For a comprehensive list of what
+ * causes reflows, visit https://gist.github.com/paulirish/5d52fb081b3570c81e3a
+ */
+
+const LINE_NUMBER_CLASS = 'diff-line-num';
+const UNFOLDABLE_LINE_CLASS = 'js-unfold';
+const NO_COMMENT_CLASS = 'no-comment-btn';
+const EMPTY_CELL_CLASS = 'empty-cell';
+const OLD_LINE_CLASS = 'old_line';
+const LINE_COLUMN_CLASSES = `.${LINE_NUMBER_CLASS}, .line_content`;
+const DIFF_CONTAINER_SELECTOR = '.files';
+const DIFF_EXPANDED_CLASS = 'diff-expanded';
+
+export default {
+  init($diffFile) {
+    /* Caching is used only when the following members are *true*. This is because there are likely to be
+     * differently configured versions of diffs in the same session. However if these values are true, they
+     * will be true in all cases */
+
+    if (!this.userCanCreateNote) {
+      // data-can-create-note is an empty string when true, otherwise undefined
+      this.userCanCreateNote = $diffFile.closest(DIFF_CONTAINER_SELECTOR).data('can-create-note') === '';
     }
 
-    textFileElement = this.getTextFileElement($currentTarget);
-    buttonParentElement.append(this.buildButton({
-      discussionID: lineContentElement.attr('data-discussion-id'),
-      lineType: lineContentElement.attr('data-line-type'),
-
-      noteableType: textFileElement.attr('data-noteable-type'),
-      noteableID: textFileElement.attr('data-noteable-id'),
-      commitID: textFileElement.attr('data-commit-id'),
-      noteType: lineContentElement.attr('data-note-type'),
-
-      // LegacyDiffNote
-      lineCode: lineContentElement.attr('data-line-code'),
-
-      // DiffNote
-      position: lineContentElement.attr('data-position')
-    }));
-  };
-
-  FilesCommentButton.prototype.hideButton = function(e) {
-    var $currentTarget = $(e.currentTarget);
-    var buttonParentElement = this.getButtonParent($currentTarget);
-
-    buttonParentElement.removeClass('is-over')
-      .nextUntil(`.${LINE_CONTENT_CLASS}`).removeClass('is-over');
-  };
-
-  FilesCommentButton.prototype.buildButton = function(buttonAttributes) {
-    return $commentButtonTemplate.clone().attr({
-      'data-discussion-id': buttonAttributes.discussionID,
-      'data-line-type': buttonAttributes.lineType,
-
-      'data-noteable-type': buttonAttributes.noteableType,
-      'data-noteable-id': buttonAttributes.noteableID,
-      'data-commit-id': buttonAttributes.commitID,
-      'data-note-type': buttonAttributes.noteType,
-
-      // LegacyDiffNote
-      'data-line-code': buttonAttributes.lineCode,
-
-      // DiffNote
-      'data-position': buttonAttributes.position
-    });
-  };
-
-  FilesCommentButton.prototype.getTextFileElement = function(hoveredElement) {
-    return hoveredElement.closest(TEXT_FILE_SELECTOR);
-  };
-
-  FilesCommentButton.prototype.getLineContent = function(hoveredElement) {
-    if (hoveredElement.hasClass(LINE_CONTENT_CLASS)) {
-      return hoveredElement;
-    }
-    if (!this.isParallelView) {
-      return $(hoveredElement).closest(LINE_HOLDER_CLASS).find("." + LINE_CONTENT_CLASS);
-    } else {
-      return $(hoveredElement).next("." + LINE_CONTENT_CLASS);
+    if (typeof notes !== 'undefined' && !this.isParallelView) {
+      this.isParallelView = notes.isParallelView && notes.isParallelView();
     }
-  };
 
-  FilesCommentButton.prototype.getButtonParent = function(hoveredElement) {
-    if (!this.isParallelView) {
-      if (hoveredElement.hasClass(OLD_LINE_CLASS)) {
-        return hoveredElement;
-      }
-      return hoveredElement.parent().find("." + OLD_LINE_CLASS);
-    } else {
-      if (hoveredElement.hasClass(LINE_NUMBER_CLASS)) {
-        return hoveredElement;
-      }
-      return $(hoveredElement).prev("." + LINE_NUMBER_CLASS);
+    if (this.userCanCreateNote) {
+      $diffFile.on('mouseover', LINE_COLUMN_CLASSES, e => this.showButton(this.isParallelView, e))
+        .on('mouseleave', LINE_COLUMN_CLASSES, e => this.hideButton(this.isParallelView, e));
     }
-  };
+  },
 
-  FilesCommentButton.prototype.validateButtonParent = function(buttonParentElement) {
-    return !buttonParentElement.hasClass(EMPTY_CELL_CLASS) && !buttonParentElement.hasClass(UNFOLDABLE_LINE_CLASS);
-  };
+  showButton(isParallelView, e) {
+    const buttonParentElement = this.getButtonParent(e.currentTarget, isParallelView);
 
-  FilesCommentButton.prototype.validateLineContent = function(lineContentElement) {
-    return lineContentElement.attr('data-note-type') && lineContentElement.attr('data-note-type') !== '';
-  };
+    if (!this.validateButtonParent(buttonParentElement)) return;
 
-  return FilesCommentButton;
-})();
+    buttonParentElement.classList.add('is-over');
+    buttonParentElement.nextElementSibling.classList.add('is-over');
+  },
 
-$.fn.filesCommentButton = function() {
-  $commentButtonTemplate = $('<button name="button" type="submit" class="add-diff-note js-add-diff-note-button" title="Add a comment to this line"><i class="fa fa-comment-o"></i></button>');
+  hideButton(isParallelView, e) {
+    const buttonParentElement = this.getButtonParent(e.currentTarget, isParallelView);
 
-  if (!(this && (this.parent().data('can-create-note') != null))) {
-    return;
-  }
-  return this.each(function() {
-    if (!$.data(this, 'filesCommentButton')) {
-      return $.data(this, 'filesCommentButton', new FilesCommentButton($(this)));
+    buttonParentElement.classList.remove('is-over');
+    buttonParentElement.nextElementSibling.classList.remove('is-over');
+  },
+
+  getButtonParent(hoveredElement, isParallelView) {
+    if (isParallelView) {
+      if (!hoveredElement.classList.contains(LINE_NUMBER_CLASS)) {
+        return hoveredElement.previousElementSibling;
+      }
+    } else if (!hoveredElement.classList.contains(OLD_LINE_CLASS)) {
+      return hoveredElement.parentNode.querySelector(`.${OLD_LINE_CLASS}`);
     }
-  });
+    return hoveredElement;
+  },
+
+  validateButtonParent(buttonParentElement) {
+    return !buttonParentElement.classList.contains(EMPTY_CELL_CLASS) &&
+      !buttonParentElement.classList.contains(UNFOLDABLE_LINE_CLASS) &&
+      !buttonParentElement.classList.contains(NO_COMMENT_CLASS) &&
+      !buttonParentElement.parentNode.classList.contains(DIFF_EXPANDED_CLASS);
+  },
 };
diff --git a/app/assets/javascripts/gfm_auto_complete.js b/app/assets/javascripts/gfm_auto_complete.js
index f99bac7da1abf8f9c31f68378fe64916f012e8de..2c56b718212826bc6f9e6c4b358b5b6b4bc97e09 100644
--- a/app/assets/javascripts/gfm_auto_complete.js
+++ b/app/assets/javascripts/gfm_auto_complete.js
@@ -1,4 +1,3 @@
-import { validEmojiNames, glEmojiTag } from './emoji';
 import glRegexp from './lib/utils/regexp';
 import AjaxCache from './lib/utils/ajax_cache';
 
@@ -373,7 +372,12 @@ class GfmAutoComplete {
     if (this.cachedData[at]) {
       this.loadData($input, at, this.cachedData[at]);
     } else if (GfmAutoComplete.atTypeMap[at] === 'emojis') {
-      this.loadData($input, at, validEmojiNames);
+      import(/* webpackChunkName: 'emoji' */ './emoji')
+        .then(({ validEmojiNames, glEmojiTag }) => {
+          this.loadData($input, at, validEmojiNames);
+          GfmAutoComplete.glEmojiTag = glEmojiTag;
+        })
+        .catch(() => { this.isLoadingData[at] = false; });
     } else {
       AjaxCache.retrieve(this.dataSources[GfmAutoComplete.atTypeMap[at]], true)
         .then((data) => {
@@ -396,6 +400,13 @@ class GfmAutoComplete {
     this.cachedData = {};
   }
 
+  destroy() {
+    this.input.each((i, input) => {
+      const $input = $(input);
+      $input.atwho('destroy');
+    });
+  }
+
   static isLoading(data) {
     let dataToInspect = data;
     if (data && data.length > 0) {
@@ -421,12 +432,14 @@ GfmAutoComplete.atTypeMap = {
 };
 
 // Emoji
+GfmAutoComplete.glEmojiTag = null;
 GfmAutoComplete.Emoji = {
   templateFunction(name) {
-    return `<li>
-      ${name} ${glEmojiTag(name)}
-    </li>
-    `;
+    // glEmojiTag helper is loaded on-demand in fetchData()
+    if (GfmAutoComplete.glEmojiTag) {
+      return `<li>${name} ${GfmAutoComplete.glEmojiTag(name)}</li>`;
+    }
+    return `<li>${name}</li>`;
   },
 };
 // Team Members
diff --git a/app/assets/javascripts/gl_form.js b/app/assets/javascripts/gl_form.js
index dc9f114af9940b728bac50d65747cc870905a56d..4e8141b2956e023ab323a3a5a937780fba8ab7c8 100644
--- a/app/assets/javascripts/gl_form.js
+++ b/app/assets/javascripts/gl_form.js
@@ -21,6 +21,9 @@ function GLForm(form, enableGFM = false) {
 GLForm.prototype.destroy = function() {
   // Clean form listeners
   this.clearEventListeners();
+  if (this.autoComplete) {
+    this.autoComplete.destroy();
+  }
   return this.form.data('gl-form', null);
 };
 
@@ -33,7 +36,8 @@ GLForm.prototype.setupForm = function() {
     this.form.addClass('gfm-form');
     // remove notify commit author checkbox for non-commit notes
     gl.utils.disableButtonIfEmptyField(this.form.find('.js-note-text'), this.form.find('.js-comment-button, .js-note-new-discussion'));
-    new GfmAutoComplete(gl.GfmAutoComplete && gl.GfmAutoComplete.dataSources).setup(this.form.find('.js-gfm-input'), {
+    this.autoComplete = new GfmAutoComplete(gl.GfmAutoComplete && gl.GfmAutoComplete.dataSources);
+    this.autoComplete.setup(this.form.find('.js-gfm-input'), {
       emojis: true,
       members: this.enableGFM,
       issues: this.enableGFM,
diff --git a/app/assets/javascripts/group_name.js b/app/assets/javascripts/group_name.js
index 462d792b8d5348229b7346edef1b518f31502dd0..37c6765d94241725a765e7b61131106bfa7bdc5a 100644
--- a/app/assets/javascripts/group_name.js
+++ b/app/assets/javascripts/group_name.js
@@ -1,13 +1,13 @@
-
+import Cookies from 'js-cookie';
 import _ from 'underscore';
 
 export default class GroupName {
   constructor() {
-    this.titleContainer = document.querySelector('.title-container');
-    this.title = document.querySelector('.title');
+    this.titleContainer = document.querySelector('.js-title-container');
+    this.title = this.titleContainer.querySelector('.title');
     this.titleWidth = this.title.offsetWidth;
-    this.groupTitle = document.querySelector('.group-title');
-    this.groups = document.querySelectorAll('.group-path');
+    this.groupTitle = this.titleContainer.querySelector('.group-title');
+    this.groups = this.titleContainer.querySelectorAll('.group-path');
     this.toggle = null;
     this.isHidden = false;
     this.init();
@@ -33,11 +33,20 @@ export default class GroupName {
 
   createToggle() {
     this.toggle = document.createElement('button');
+    this.toggle.setAttribute('type', 'button');
     this.toggle.className = 'text-expander group-name-toggle';
     this.toggle.setAttribute('aria-label', 'Toggle full path');
-    this.toggle.innerHTML = '...';
+    if (Cookies.get('new_nav') === 'true') {
+      this.toggle.innerHTML = '<i class="fa fa-ellipsis-h" aria-hidden="true"></i>';
+    } else {
+      this.toggle.innerHTML = '...';
+    }
     this.toggle.addEventListener('click', this.toggleGroups.bind(this));
-    this.titleContainer.insertBefore(this.toggle, this.title);
+    if (Cookies.get('new_nav') === 'true') {
+      this.title.insertBefore(this.toggle, this.groupTitle);
+    } else {
+      this.titleContainer.insertBefore(this.toggle, this.title);
+    }
     this.toggleGroups();
   }
 
diff --git a/app/assets/javascripts/issuable_bulk_update_sidebar.js b/app/assets/javascripts/issuable_bulk_update_sidebar.js
index a8856120c5e6c25f4eba5d250e5dba183f960422..4f376599ba98a6ed4365e6140ee45ac316e4a306 100644
--- a/app/assets/javascripts/issuable_bulk_update_sidebar.js
+++ b/app/assets/javascripts/issuable_bulk_update_sidebar.js
@@ -5,6 +5,7 @@
 /* global SubscriptionSelect */
 
 import IssuableBulkUpdateActions from './issuable_bulk_update_actions';
+import SidebarHeightManager from './sidebar_height_manager';
 
 const HIDDEN_CLASS = 'hidden';
 const DISABLED_CONTENT_CLASS = 'disabled-content';
@@ -56,18 +57,6 @@ export default class IssuableBulkUpdateSidebar {
     return navbarHeight + layoutNavHeight + subNavScroll;
   }
 
-  initSidebar() {
-    if (!this.navHeight) {
-      this.navHeight = this.getNavHeight();
-    }
-
-    if (!this.sidebarInitialized) {
-      $(document).off('scroll').on('scroll', _.throttle(this.setSidebarHeight, 10).bind(this));
-      $(window).off('resize').on('resize', _.throttle(this.setSidebarHeight, 10).bind(this));
-      this.sidebarInitialized = true;
-    }
-  }
-
   setupBulkUpdateActions() {
     IssuableBulkUpdateActions.setOriginalDropdownData();
   }
@@ -97,7 +86,7 @@ export default class IssuableBulkUpdateSidebar {
     this.toggleCheckboxDisplay(enable);
 
     if (enable) {
-      this.initSidebar();
+      SidebarHeightManager.init();
     }
   }
 
@@ -143,17 +132,6 @@ export default class IssuableBulkUpdateSidebar {
       this.$bulkEditSubmitBtn.enable();
     }
   }
-  // loosely based on method of the same name in right_sidebar.js
-  setSidebarHeight() {
-    const currentScrollDepth = window.pageYOffset || 0;
-    const diff = this.navHeight - currentScrollDepth;
-
-    if (diff > 0) {
-      this.$sidebar.outerHeight(window.innerHeight - diff);
-    } else {
-      this.$sidebar.outerHeight('100%');
-    }
-  }
 
   static getCheckedIssueIds() {
     const $checkedIssues = $('.selected_issue:checked');
diff --git a/app/assets/javascripts/jobs/components/sidebar_details_block.vue b/app/assets/javascripts/jobs/components/sidebar_details_block.vue
index 4223a8fea497b58666c412ce8b6b87c8052c7a40..d0145fed3964908dfb84c697489e8fb50383214b 100644
--- a/app/assets/javascripts/jobs/components/sidebar_details_block.vue
+++ b/app/assets/javascripts/jobs/components/sidebar_details_block.vue
@@ -39,6 +39,17 @@
       runnerId() {
         return `#${this.job.runner.id}`;
       },
+      renderBlock() {
+        return this.job.merge_request ||
+          this.job.duration ||
+          this.job.finished_data ||
+          this.job.erased_at ||
+          this.job.queued ||
+          this.job.runner ||
+          this.job.coverage ||
+          this.job.tags.length ||
+          this.job.cancel_path;
+      },
     },
   };
 </script>
@@ -63,7 +74,7 @@
           Retry
         </a>
       </div>
-      <div class="block">
+      <div :class="{block : renderBlock }">
         <p
           class="build-detail-row js-job-mr"
           v-if="job.merge_request">
diff --git a/app/assets/javascripts/lib/utils/datetime_utility.js b/app/assets/javascripts/lib/utils/datetime_utility.js
index bfcc50996ccef57a249aa05d56fd8ed6066d93fb..1d1763c39631b9e6707641b3d8b96f4bbd0f10a2 100644
--- a/app/assets/javascripts/lib/utils/datetime_utility.js
+++ b/app/assets/javascripts/lib/utils/datetime_utility.js
@@ -112,29 +112,11 @@ window.dateFormat = dateFormat;
       return timefor;
     };
 
-    w.gl.utils.cachedTimeagoElements = [];
     w.gl.utils.renderTimeago = function($els) {
-      if (!$els && !w.gl.utils.cachedTimeagoElements.length) {
-        w.gl.utils.cachedTimeagoElements = [].slice.call(document.querySelectorAll('.js-timeago-render'));
-      } else if ($els) {
-        w.gl.utils.cachedTimeagoElements = w.gl.utils.cachedTimeagoElements.concat($els.toArray());
-      }
-
-      w.gl.utils.cachedTimeagoElements.forEach(gl.utils.updateTimeagoText);
-    };
-
-    w.gl.utils.updateTimeagoText = function(el) {
-      const formattedDate = gl.utils.getTimeago().format(el.getAttribute('datetime'), lang);
-
-      if (el.textContent !== formattedDate) {
-        el.textContent = formattedDate;
-      }
-    };
-
-    w.gl.utils.initTimeagoTimeout = function() {
-      gl.utils.renderTimeago();
+      const timeagoEls = $els || document.querySelectorAll('.js-timeago-render');
 
-      gl.utils.timeagoTimeout = setTimeout(gl.utils.initTimeagoTimeout, 1000);
+      // timeago.js sets timeouts internally for each timeago value to be updated in real time
+      gl.utils.getTimeago().render(timeagoEls, lang);
     };
 
     w.gl.utils.getDayDifference = function(a, b) {
diff --git a/app/assets/javascripts/main.js b/app/assets/javascripts/main.js
index d27b4ec78c6226015870f85a0cacbf40e99d95bb..fe752d95b903c255b38895fc8793d32976719f63 100644
--- a/app/assets/javascripts/main.js
+++ b/app/assets/javascripts/main.js
@@ -70,7 +70,7 @@ import './ajax_loading_spinner';
 import './api';
 import './aside';
 import './autosave';
-import AwardsHandler from './awards_handler';
+import loadAwardsHandler from './awards_handler';
 import './breakpoints';
 import './broadcast_message';
 import './build';
@@ -355,10 +355,10 @@ $(function () {
   $window.off('resize.app').on('resize.app', function () {
     return fitSidebarForSize();
   });
-  gl.awardsHandler = new AwardsHandler();
+  loadAwardsHandler();
   new Aside();
 
-  gl.utils.initTimeagoTimeout();
+  gl.utils.renderTimeago();
 
   $(document).trigger('init.scrolling-tabs');
 });
diff --git a/app/assets/javascripts/merge_request_tabs.js b/app/assets/javascripts/merge_request_tabs.js
index 3cf3233cc65a37e63f734cefc0cbbf8349933c7b..7840f05a8aefd5ac25009012014c84ecb0b74ee6 100644
--- a/app/assets/javascripts/merge_request_tabs.js
+++ b/app/assets/javascripts/merge_request_tabs.js
@@ -144,7 +144,9 @@ import BlobForkSuggestion from './blob/blob_fork_suggestion';
         this.resetViewContainer();
         this.mountPipelinesView();
       } else {
-        this.expandView();
+        if (Breakpoints.get().getBreakpointSize() !== 'xs') {
+          this.expandView();
+        }
         this.resetViewContainer();
         this.destroyPipelinesView();
       }
diff --git a/app/assets/javascripts/notes.js b/app/assets/javascripts/notes.js
index 34476f3303f27824644516e5b39e147a3fc1ce2f..555b8c8a65cbfd2099a910b30ea2d84f1ff7b272 100644
--- a/app/assets/javascripts/notes.js
+++ b/app/assets/javascripts/notes.js
@@ -18,6 +18,7 @@ import 'vendor/jquery.caret'; // required by jquery.atwho
 import 'vendor/jquery.atwho';
 import AjaxCache from '~/lib/utils/ajax_cache';
 import CommentTypeToggle from './comment_type_toggle';
+import loadAwardsHandler from './awards_handler';
 import './autosave';
 import './dropzone_input';
 import './task_list';
@@ -291,8 +292,13 @@ export default class Notes {
 
       if ('emoji_award' in noteEntity.commands_changes) {
         votesBlock = $('.js-awards-block').eq(0);
-        gl.awardsHandler.addAwardToEmojiBar(votesBlock, noteEntity.commands_changes.emoji_award);
-        return gl.awardsHandler.scrollToAwards();
+
+        loadAwardsHandler().then((awardsHandler) => {
+          awardsHandler.addAwardToEmojiBar(votesBlock, noteEntity.commands_changes.emoji_award);
+          awardsHandler.scrollToAwards();
+        }).catch(() => {
+          // ignore
+        });
       }
     }
   }
@@ -337,6 +343,10 @@ export default class Notes {
 
     if (!noteEntity.valid) {
       if (noteEntity.errors.commands_only) {
+        if (noteEntity.commands_changes &&
+            Object.keys(noteEntity.commands_changes).length > 0) {
+          $notesList.find('.system-note.being-posted').remove();
+        }
         this.addFlash(noteEntity.errors.commands_only, 'notice', this.parentTimeline);
         this.refresh();
       }
@@ -829,6 +839,8 @@ export default class Notes {
    */
   setupDiscussionNoteForm(dataHolder, form) {
     // setup note target
+    const diffFileData = dataHolder.closest('.text-file');
+
     var discussionID = dataHolder.data('discussionId');
 
     if (discussionID) {
@@ -839,9 +851,10 @@ export default class Notes {
     form.attr('data-line-code', dataHolder.data('lineCode'));
     form.find('#line_type').val(dataHolder.data('lineType'));
 
-    form.find('#note_noteable_type').val(dataHolder.data('noteableType'));
-    form.find('#note_noteable_id').val(dataHolder.data('noteableId'));
-    form.find('#note_commit_id').val(dataHolder.data('commitId'));
+    form.find('#note_noteable_type').val(diffFileData.data('noteableType'));
+    form.find('#note_noteable_id').val(diffFileData.data('noteableId'));
+    form.find('#note_commit_id').val(diffFileData.data('commitId'));
+
     form.find('#note_type').val(dataHolder.data('noteType'));
 
     // LegacyDiffNote
diff --git a/app/assets/javascripts/right_sidebar.js b/app/assets/javascripts/right_sidebar.js
index 322162afdb8832b06aac5f2709654a006d204efe..d8f1fe10b269484f0fafd40936032062ea744547 100644
--- a/app/assets/javascripts/right_sidebar.js
+++ b/app/assets/javascripts/right_sidebar.js
@@ -1,6 +1,7 @@
 /* eslint-disable func-names, space-before-function-paren, no-var, prefer-rest-params, wrap-iife, no-unused-vars, consistent-return, one-var, one-var-declaration-per-line, quotes, prefer-template, object-shorthand, comma-dangle, no-else-return, no-param-reassign, max-len */
 
 import Cookies from 'js-cookie';
+import SidebarHeightManager from './sidebar_height_manager';
 
 (function() {
   this.Sidebar = (function() {
@@ -8,10 +9,6 @@ import Cookies from 'js-cookie';
       this.toggleTodo = this.toggleTodo.bind(this);
       this.sidebar = $('aside');
 
-      this.$sidebarInner = this.sidebar.find('.issuable-sidebar');
-      this.$navGitlab = $('.navbar-gitlab');
-      this.$rightSidebar = $('.js-right-sidebar');
-
       this.removeListeners();
       this.addEventListeners();
     }
@@ -25,16 +22,14 @@ import Cookies from 'js-cookie';
     };
 
     Sidebar.prototype.addEventListeners = function() {
+      SidebarHeightManager.init();
       const $document = $(document);
-      const throttledSetSidebarHeight = _.throttle(this.setSidebarHeight.bind(this), 20);
-      const debouncedSetSidebarHeight = _.debounce(this.setSidebarHeight.bind(this), 200);
 
       this.sidebar.on('click', '.sidebar-collapsed-icon', this, this.sidebarCollapseClicked);
       $('.dropdown').on('hidden.gl.dropdown', this, this.onSidebarDropdownHidden);
       $('.dropdown').on('loading.gl.dropdown', this.sidebarDropdownLoading);
       $('.dropdown').on('loaded.gl.dropdown', this.sidebarDropdownLoaded);
-      $(window).on('resize', () => throttledSetSidebarHeight());
-      $document.on('scroll', () => debouncedSetSidebarHeight());
+
       $document.on('click', '.js-sidebar-toggle', function(e, triggered) {
         var $allGutterToggleIcons, $this, $thisIcon;
         e.preventDefault();
@@ -212,18 +207,6 @@ import Cookies from 'js-cookie';
       }
     };
 
-    Sidebar.prototype.setSidebarHeight = function() {
-      const $navHeight = this.$navGitlab.outerHeight();
-      const diff = $navHeight - $(window).scrollTop();
-      if (diff > 0) {
-        this.$rightSidebar.outerHeight($(window).height() - diff);
-        this.$sidebarInner.height('100%');
-      } else {
-        this.$rightSidebar.outerHeight('100%');
-        this.$sidebarInner.height('');
-      }
-    };
-
     Sidebar.prototype.isOpen = function() {
       return this.sidebar.is('.right-sidebar-expanded');
     };
diff --git a/app/assets/javascripts/sidebar_height_manager.js b/app/assets/javascripts/sidebar_height_manager.js
new file mode 100644
index 0000000000000000000000000000000000000000..022415f22b2cffb08f8707772b230f5180ae5717
--- /dev/null
+++ b/app/assets/javascripts/sidebar_height_manager.js
@@ -0,0 +1,33 @@
+export default {
+  init() {
+    if (!this.initialized) {
+      this.$window = $(window);
+      this.$rightSidebar = $('.js-right-sidebar');
+      this.$navHeight = $('.navbar-gitlab').outerHeight() +
+        $('.layout-nav').outerHeight() +
+        $('.sub-nav-scroll').outerHeight();
+
+      const throttledSetSidebarHeight = _.throttle(() => this.setSidebarHeight(), 20);
+      const debouncedSetSidebarHeight = _.debounce(() => this.setSidebarHeight(), 200);
+
+      this.$window.on('scroll', throttledSetSidebarHeight);
+      this.$window.on('resize', debouncedSetSidebarHeight);
+      this.initialized = true;
+    }
+  },
+
+  setSidebarHeight() {
+    const currentScrollDepth = window.pageYOffset || 0;
+    const diff = this.$navHeight - currentScrollDepth;
+
+    if (diff > 0) {
+      const newSidebarHeight = window.innerHeight - diff;
+      this.$rightSidebar.outerHeight(newSidebarHeight);
+      this.sidebarHeightIsCustom = true;
+    } else if (this.sidebarHeightIsCustom) {
+      this.$rightSidebar.outerHeight('100%');
+      this.sidebarHeightIsCustom = false;
+    }
+  },
+};
+
diff --git a/app/assets/javascripts/single_file_diff.js b/app/assets/javascripts/single_file_diff.js
index c44892dae3d21cce0ae299479fd0e399774e87f2..9316a2af0b7643941c446ba7d7dd0e40481dd59d 100644
--- a/app/assets/javascripts/single_file_diff.js
+++ b/app/assets/javascripts/single_file_diff.js
@@ -1,5 +1,7 @@
 /* eslint-disable func-names, prefer-arrow-callback, space-before-function-paren, no-var, prefer-rest-params, wrap-iife, one-var, one-var-declaration-per-line, consistent-return, no-param-reassign, max-len */
 
+import FilesCommentButton from './files_comment_button';
+
 (function() {
   window.SingleFileDiff = (function() {
     var COLLAPSED_HTML, ERROR_HTML, LOADING_HTML, WRAPPER;
@@ -78,6 +80,8 @@
             gl.diffNotesCompileComponents();
           }
 
+          FilesCommentButton.init($(_this.file));
+
           if (cb) cb();
         };
       })(this));
diff --git a/app/assets/javascripts/vue_shared/components/markdown/field.vue b/app/assets/javascripts/vue_shared/components/markdown/field.vue
index e6977681e96e052179f6f34ef35f9bfdd6ab3b99..8303c556f6451a4d4fcc8ef54174246d5bd7fa6d 100644
--- a/app/assets/javascripts/vue_shared/components/markdown/field.vue
+++ b/app/assets/javascripts/vue_shared/components/markdown/field.vue
@@ -64,6 +64,12 @@
       */
       return new gl.GLForm($(this.$refs['gl-form']), true);
     },
+    beforeDestroy() {
+      const glForm = $(this.$refs['gl-form']).data('gl-form');
+      if (glForm) {
+        glForm.destroy();
+      }
+    },
   };
 </script>
 
diff --git a/app/assets/javascripts/webpack.js b/app/assets/javascripts/webpack.js
new file mode 100644
index 0000000000000000000000000000000000000000..9a9cf395fb850977ade734f666390de26ce046bb
--- /dev/null
+++ b/app/assets/javascripts/webpack.js
@@ -0,0 +1,9 @@
+/**
+ * This is the first script loaded by webpack's runtime. It is used to manually configure
+ * config.output.publicPath to account for relative_url_root or CDN settings which cannot be
+ * baked-in to our webpack bundles.
+ */
+
+if (gon && gon.webpack_public_path) {
+  __webpack_public_path__ = gon.webpack_public_path; // eslint-disable-line
+}
diff --git a/app/assets/stylesheets/framework/files.scss b/app/assets/stylesheets/framework/files.scss
index 245117b2559f9a32bd176b70929eb530128e85de..c7c2684d548524ae1ebb78e1d3fd37ed72b9821d 100644
--- a/app/assets/stylesheets/framework/files.scss
+++ b/app/assets/stylesheets/framework/files.scss
@@ -17,6 +17,8 @@
       max-width: $limited-layout-width-sm;
       margin-left: auto;
       margin-right: auto;
+      padding-top: 64px;
+      padding-bottom: 64px;
     }
   }
 
diff --git a/app/assets/stylesheets/new_nav.scss b/app/assets/stylesheets/new_nav.scss
index 441bfc479f6e8a7aa08dba987c0a9078e689c53f..bfb7a0c7e25ebd9f3af38dd3d1358002d4719b19 100644
--- a/app/assets/stylesheets/new_nav.scss
+++ b/app/assets/stylesheets/new_nav.scss
@@ -11,20 +11,19 @@ header.navbar-gitlab-new {
     padding-left: 0;
 
     .title-container {
+      align-items: stretch;
       padding-top: 0;
       overflow: visible;
     }
 
     .title {
-      display: block;
-      height: 100%;
+      display: flex;
       padding-right: 0;
       color: currentColor;
 
       > a {
         display: flex;
         align-items: center;
-        height: 100%;
         padding-top: 3px;
         padding-right: $gl-padding;
         padding-left: $gl-padding;
@@ -265,3 +264,127 @@ header.navbar-gitlab-new {
     }
   }
 }
+
+.breadcrumbs {
+  display: flex;
+  min-height: 60px;
+  padding-top: $gl-padding-top;
+  padding-bottom: $gl-padding-top;
+  color: $gl-text-color;
+  border-bottom: 1px solid $border-color;
+
+  .dropdown-toggle-caret {
+    position: relative;
+    top: -1px;
+    padding: 0 5px;
+    color: rgba($black, .65);
+    font-size: 10px;
+    line-height: 1;
+    background: none;
+    border: 0;
+
+    &:focus {
+      outline: 0;
+    }
+  }
+}
+
+.breadcrumbs-container {
+  display: flex;
+  width: 100%;
+  position: relative;
+
+  .dropdown-menu-projects {
+    margin-top: -$gl-padding;
+    margin-left: $gl-padding;
+  }
+}
+
+.breadcrumbs-links {
+  flex: 1;
+  align-self: center;
+  color: $black-transparent;
+
+  a {
+    color: rgba($black, .65);
+
+    &:not(:first-child),
+    &.group-path {
+      margin-left: 4px;
+    }
+
+    &:not(:last-of-type),
+    &.group-path {
+      margin-right: 3px;
+    }
+  }
+
+  .title {
+    white-space: nowrap;
+
+    > a {
+      &:last-of-type {
+        font-weight: 600;
+      }
+    }
+  }
+
+  .avatar-tile {
+    margin-right: 5px;
+    border: 1px solid $border-color;
+    border-radius: 50%;
+    vertical-align: sub;
+
+    &.identicon {
+      float: left;
+      width: 16px;
+      height: 16px;
+      margin-top: 2px;
+      font-size: 10px;
+    }
+  }
+
+  .text-expander {
+    margin-left: 4px;
+    margin-right: 4px;
+
+    > i {
+      position: relative;
+      top: 1px;
+    }
+  }
+}
+
+.breadcrumbs-extra {
+  flex: 0 0 auto;
+  margin-left: auto;
+}
+
+.breadcrumbs-sub-title {
+  margin: 2px 0 0;
+  font-size: 16px;
+  font-weight: normal;
+
+  ul {
+    margin: 0;
+  }
+
+  li {
+    display: inline-block;
+
+    &:not(:last-child) {
+      &::after {
+        content: "/";
+        margin: 0 2px 0 5px;
+      }
+    }
+
+    &:last-child a {
+      font-weight: 600;
+    }
+  }
+
+  a {
+    color: $gl-text-color;
+  }
+}
diff --git a/app/assets/stylesheets/new_sidebar.scss b/app/assets/stylesheets/new_sidebar.scss
index be4cc02b3ea03294f3793c20e8fdf6e575d63919..17f23f7fce366261ffeabf270e23b627d7cf7d3d 100644
--- a/app/assets/stylesheets/new_sidebar.scss
+++ b/app/assets/stylesheets/new_sidebar.scss
@@ -49,6 +49,7 @@ $new-sidebar-width: 220px;
   position: fixed;
   z-index: 400;
   width: $new-sidebar-width;
+  transition: width $sidebar-transition-duration;
   top: 50px;
   bottom: 0;
   left: 0;
@@ -62,6 +63,8 @@ $new-sidebar-width: 220px;
   }
 
   li {
+    white-space: nowrap;
+
     a {
       display: block;
       padding: 12px 14px;
@@ -72,6 +75,10 @@ $new-sidebar-width: 220px;
     color: $gl-text-color;
     text-decoration: none;
   }
+
+  @media (max-width: $screen-xs-max) {
+    width: 0;
+  }
 }
 
 .sidebar-sub-level-items {
diff --git a/app/assets/stylesheets/pages/diff.scss b/app/assets/stylesheets/pages/diff.scss
index b58922626fa72fc90dd2a88f3507aa2679609e5a..55011e8a21b7f385504cb9a8147714fc4953c969 100644
--- a/app/assets/stylesheets/pages/diff.scss
+++ b/app/assets/stylesheets/pages/diff.scss
@@ -20,8 +20,6 @@
   }
 
   .diff-content {
-    overflow: auto;
-    overflow-y: hidden;
     background: $white-light;
     color: $gl-text-color;
     border-radius: 0 0 3px 3px;
@@ -476,6 +474,7 @@
   height: 19px;
   width: 19px;
   margin-left: -15px;
+  z-index: 100;
 
   &:hover {
     .diff-comment-avatar,
@@ -491,7 +490,7 @@
           transform: translateX((($i * $x-pos) - $x-pos));
 
           &:hover {
-            transform: translateX((($i * $x-pos) - $x-pos)) scale(1.2);
+            transform: translateX((($i * $x-pos) - $x-pos));
           }
         }
       }
@@ -542,6 +541,7 @@
   height: 19px;
   padding: 0;
   transition: transform .1s ease-out;
+  z-index: 100;
 
   svg {
     position: absolute;
@@ -555,10 +555,6 @@
     fill: $white-light;
   }
 
-  &:hover {
-    transform: scale(1.2);
-  }
-
   &:focus {
     outline: 0;
   }
diff --git a/app/assets/stylesheets/pages/issuable.scss b/app/assets/stylesheets/pages/issuable.scss
index e3ebcc8af6cee1734dec57efee6847f024078f18..057d457b3a2be3676178f7d896a875a0c397adca 100644
--- a/app/assets/stylesheets/pages/issuable.scss
+++ b/app/assets/stylesheets/pages/issuable.scss
@@ -597,7 +597,38 @@
     .issue-info-container {
       -webkit-flex: 1;
       flex: 1;
+      display: flex;
       padding-right: $gl-padding;
+
+      .issue-main-info {
+        flex: 1 auto;
+        margin-right: 10px;
+      }
+
+      .issuable-meta {
+        display: flex;
+        flex-direction: column;
+        align-items: flex-end;
+        flex: 1 0 auto;
+
+        .controls {
+          margin-bottom: 2px;
+          line-height: 20px;
+          padding: 0;
+        }
+
+        .issue-updated-at {
+          line-height: 20px;
+        }
+      }
+
+      @media(max-width: $screen-xs-max) {
+        .issuable-meta {
+          .controls li {
+            margin-right: 0;
+          }
+        }
+      }
     }
 
     .issue-check {
@@ -609,6 +640,30 @@
         vertical-align: text-top;
       }
     }
+
+    .issuable-milestone,
+    .issuable-info,
+    .task-status,
+    .issuable-updated-at {
+      font-weight: normal;
+      color: $gl-text-color-secondary;
+
+      a {
+        color: $gl-text-color;
+
+        .fa {
+          color: $gl-text-color-secondary;
+        }
+      }
+    }
+
+    @media(max-width: $screen-md-max) {
+      .task-status,
+      .issuable-due-date,
+      .project-ref-path {
+        display: none;
+      }
+    }
   }
 }
 
diff --git a/app/assets/stylesheets/pages/labels.scss b/app/assets/stylesheets/pages/labels.scss
index b158416b9402d8c1e6710f8b1c955fd36f818aa4..ee48f7a36262d5298b71a4e14584bdec3cb8fc18 100644
--- a/app/assets/stylesheets/pages/labels.scss
+++ b/app/assets/stylesheets/pages/labels.scss
@@ -279,5 +279,9 @@
 
 .label-link {
   display: inline-block;
-  vertical-align: text-top;
+  vertical-align: top;
+
+  .label {
+    vertical-align: inherit;
+  }
 }
diff --git a/app/assets/stylesheets/pages/notes.scss b/app/assets/stylesheets/pages/notes.scss
index 53d5cf2f7bc3f3536312189cf5d0d1cc242f23a4..303425041dfc1f2885d4ad30d38386e500e41476 100644
--- a/app/assets/stylesheets/pages/notes.scss
+++ b/app/assets/stylesheets/pages/notes.scss
@@ -628,8 +628,14 @@ ul.notes {
  * Line note button on the side of diffs
  */
 
+.line_holder .is-over:not(.no-comment-btn) {
+  .add-diff-note {
+    opacity: 1;
+  }
+}
+
 .add-diff-note {
-  display: none;
+  opacity: 0;
   margin-top: -2px;
   border-radius: 50%;
   background: $white-light;
@@ -642,13 +648,11 @@ ul.notes {
   width: 23px;
   height: 23px;
   border: 1px solid $blue-500;
-  transition: transform .1s ease-in-out;
 
   &:hover {
     background: $blue-500;
     border-color: $blue-600;
     color: $white-light;
-    transform: scale(1.15);
   }
 
   &:active {
diff --git a/app/assets/stylesheets/pages/projects.scss b/app/assets/stylesheets/pages/projects.scss
index ba530bf7f9be50569b0df97d457e77dda12b921d..7d7c34115f9a77de5904c3e028717e8dcb0416a7 100644
--- a/app/assets/stylesheets/pages/projects.scss
+++ b/app/assets/stylesheets/pages/projects.scss
@@ -483,11 +483,12 @@ a.deploy-project-label {
 .project-stats {
   font-size: 0;
   text-align: center;
+  max-width: 100%;
+  border-bottom: 1px solid $border-color;
 
   .nav {
     padding-top: 12px;
     padding-bottom: 12px;
-    border-bottom: 1px solid $border-color;
   }
 
   .nav > li {
diff --git a/app/assets/stylesheets/pages/tree.scss b/app/assets/stylesheets/pages/tree.scss
index 9b2ed0d68a12f82e70bc036ac5c4082374c82ebe..dc88cf3e699f0137ed4742eabba410ff9777cd70 100644
--- a/app/assets/stylesheets/pages/tree.scss
+++ b/app/assets/stylesheets/pages/tree.scss
@@ -1,4 +1,5 @@
 .tree-holder {
+
   .nav-block {
     margin: 10px 0;
 
@@ -15,6 +16,11 @@
         .btn-group {
           margin-left: 10px;
         }
+
+        .control {
+          float: left;
+          margin-left: 10px;
+        }
       }
 
       .tree-ref-holder {
diff --git a/app/controllers/groups/milestones_controller.rb b/app/controllers/groups/milestones_controller.rb
index e52fa7660447321b35156941802c5720d288519e..6b1d418fc9a07f11e55088923883786cfe584b03 100644
--- a/app/controllers/groups/milestones_controller.rb
+++ b/app/controllers/groups/milestones_controller.rb
@@ -11,6 +11,9 @@ class Groups::MilestonesController < Groups::ApplicationController
         @milestone_states = GlobalMilestone.states_count(@projects)
         @milestones = Kaminari.paginate_array(milestones).page(params[:page])
       end
+      format.json do
+        render json: milestones.map { |m| m.for_display.slice(:title, :name) }
+      end
     end
   end
 
diff --git a/app/controllers/projects/issues_controller.rb b/app/controllers/projects/issues_controller.rb
index 5b2de93c1680a9b1fad4fe44848b37792fb60de9..ca483c105b62b09b5ead43e0fd23830d368743b4 100644
--- a/app/controllers/projects/issues_controller.rb
+++ b/app/controllers/projects/issues_controller.rb
@@ -227,7 +227,7 @@ class Projects::IssuesController < Projects::ApplicationController
   def issue
     return @issue if defined?(@issue)
     # The Sortable default scope causes performance issues when used with find_by
-    @noteable = @issue ||= @project.issues.find_by!(iid: params[:id])
+    @noteable = @issue ||= @project.issues.where(iid: params[:id]).reorder(nil).take!
 
     return render_404 unless can?(current_user, :read_issue, @issue)
 
@@ -267,10 +267,22 @@ class Projects::IssuesController < Projects::ApplicationController
   end
 
   def issue_params
-    params.require(:issue).permit(
-      :title, :assignee_id, :position, :description, :confidential,
-      :milestone_id, :due_date, :state_event, :task_num, :lock_version, label_ids: [], assignee_ids: []
-    )
+    params.require(:issue).permit(*issue_params_attributes)
+  end
+
+  def issue_params_attributes
+    %i[
+      title
+      assignee_id
+      position
+      description
+      confidential
+      milestone_id
+      due_date
+      state_event
+      task_num
+      lock_version
+    ] + [{ label_ids: [], assignee_ids: [] }]
   end
 
   def authenticate_user!
diff --git a/app/finders/issuable_finder.rb b/app/finders/issuable_finder.rb
index 558f8b5e2e5dc46bd61b9f7b237372c2e96676fd..7bc2117f61eff617f97dc4a216fba3663165efa0 100644
--- a/app/finders/issuable_finder.rb
+++ b/app/finders/issuable_finder.rb
@@ -20,6 +20,7 @@
 #
 class IssuableFinder
   NONE = '0'.freeze
+  IRRELEVANT_PARAMS_FOR_CACHE_KEY = %i[utf8 sort page].freeze
 
   attr_accessor :current_user, :params
 
@@ -62,7 +63,7 @@ class IssuableFinder
   # grouping and counting within that query.
   #
   def count_by_state
-    count_params = params.merge(state: nil, sort: nil)
+    count_params = params.merge(state: nil, sort: nil, for_counting: true)
     labels_count = label_names.any? ? label_names.count : 1
     finder = self.class.new(current_user, count_params)
     counts = Hash.new(0)
@@ -86,6 +87,10 @@ class IssuableFinder
     execute.find_by!(*params)
   end
 
+  def state_counter_cache_key(state)
+    Digest::SHA1.hexdigest(state_counter_cache_key_components(state).flatten.join('-'))
+  end
+
   def group
     return @group if defined?(@group)
 
@@ -418,4 +423,13 @@ class IssuableFinder
   def current_user_related?
     params[:scope] == 'created-by-me' || params[:scope] == 'authored' || params[:scope] == 'assigned-to-me'
   end
+
+  def state_counter_cache_key_components(state)
+    opts = params.with_indifferent_access
+    opts[:state] = state
+    opts.except!(*IRRELEVANT_PARAMS_FOR_CACHE_KEY)
+    opts.delete_if { |_, value| value.blank? }
+
+    ['issuables_count', klass.to_ability_name, opts.sort]
+  end
 end
diff --git a/app/finders/issues_finder.rb b/app/finders/issues_finder.rb
index 3da5508aefd077ba3281e60d31b58ddd25d7a5bb..85230ff1293cbbb7038acf92e6f507d12b52f072 100644
--- a/app/finders/issues_finder.rb
+++ b/app/finders/issues_finder.rb
@@ -16,14 +16,72 @@
 #     sort: string
 #
 class IssuesFinder < IssuableFinder
+  CONFIDENTIAL_ACCESS_LEVEL = Gitlab::Access::REPORTER
+
   def klass
     Issue
   end
 
+  def with_confidentiality_access_check
+    return Issue.all if user_can_see_all_confidential_issues?
+    return Issue.where('issues.confidential IS NOT TRUE') if user_cannot_see_confidential_issues?
+
+    Issue.where('
+      issues.confidential IS NOT TRUE
+      OR (issues.confidential = TRUE
+        AND (issues.author_id = :user_id
+          OR EXISTS (SELECT TRUE FROM issue_assignees WHERE user_id = :user_id AND issue_id = issues.id)
+          OR issues.project_id IN(:project_ids)))',
+      user_id: current_user.id,
+      project_ids: current_user.authorized_projects(CONFIDENTIAL_ACCESS_LEVEL).select(:id))
+  end
+
   private
 
   def init_collection
-    IssuesFinder.not_restricted_by_confidentiality(current_user)
+    with_confidentiality_access_check
+  end
+
+  def user_can_see_all_confidential_issues?
+    return @user_can_see_all_confidential_issues if defined?(@user_can_see_all_confidential_issues)
+
+    return @user_can_see_all_confidential_issues = false if current_user.blank?
+    return @user_can_see_all_confidential_issues = true if current_user.full_private_access?
+
+    @user_can_see_all_confidential_issues =
+      project? &&
+      project &&
+      project.team.max_member_access(current_user.id) >= CONFIDENTIAL_ACCESS_LEVEL
+  end
+
+  # Anonymous users can't see any confidential issues.
+  #
+  # Users without access to see _all_ confidential issues (as in
+  # `user_can_see_all_confidential_issues?`) are more complicated, because they
+  # can see confidential issues where:
+  # 1. They are an assignee.
+  # 2. They are an author.
+  #
+  # That's fine for most cases, but if we're just counting, we need to cache
+  # effectively. If we cached this accurately, we'd have a cache key for every
+  # authenticated user without sufficient access to the project. Instead, when
+  # we are counting, we treat them as if they can't see any confidential issues.
+  #
+  # This does mean the counts may be wrong for those users, but avoids an
+  # explosion in cache keys.
+  def user_cannot_see_confidential_issues?(for_counting: false)
+    return false if user_can_see_all_confidential_issues?
+
+    current_user.blank? || for_counting || params[:for_counting]
+  end
+
+  def state_counter_cache_key_components(state)
+    extra_components = [
+      user_can_see_all_confidential_issues?,
+      user_cannot_see_confidential_issues?(for_counting: true)
+    ]
+
+    super + extra_components
   end
 
   def by_assignee(items)
@@ -38,21 +96,6 @@ class IssuesFinder < IssuableFinder
     end
   end
 
-  def self.not_restricted_by_confidentiality(user)
-    return Issue.where('issues.confidential IS NOT TRUE') if user.blank?
-
-    return Issue.all if user.full_private_access?
-
-    Issue.where('
-      issues.confidential IS NOT TRUE
-      OR (issues.confidential = TRUE
-        AND (issues.author_id = :user_id
-          OR EXISTS (SELECT TRUE FROM issue_assignees WHERE user_id = :user_id AND issue_id = issues.id)
-          OR issues.project_id IN(:project_ids)))',
-      user_id: user.id,
-      project_ids: user.authorized_projects(Gitlab::Access::REPORTER).select(:id))
-  end
-
   def item_project_ids(items)
     items&.reorder(nil)&.select(:project_id)
   end
diff --git a/app/helpers/groups_helper.rb b/app/helpers/groups_helper.rb
index eb45241615f23527d0a09fa13c60598d95473cf3..af0b3e9c5bc0f351060183075191cd21d60da0ee 100644
--- a/app/helpers/groups_helper.rb
+++ b/app/helpers/groups_helper.rb
@@ -16,11 +16,12 @@ module GroupsHelper
     full_title = ''
 
     group.ancestors.reverse.each do |parent|
-      full_title += link_to(simple_sanitize(parent.name), group_path(parent), class: 'group-path hidable')
+      full_title += group_title_link(parent, hidable: true)
+
       full_title += '<span class="hidable"> / </span>'.html_safe
     end
 
-    full_title += link_to(simple_sanitize(group.name), group_path(group), class: 'group-path')
+    full_title += group_title_link(group)
     full_title += ' &middot; '.html_safe + link_to(simple_sanitize(name), url, class: 'group-path') if name
 
     content_tag :span, class: 'group-title' do
@@ -56,4 +57,20 @@ module GroupsHelper
   def group_issues(group)
     IssuesFinder.new(current_user, group_id: group.id).execute
   end
+
+  private
+
+  def group_title_link(group, hidable: false)
+    link_to(group_path(group), class: "group-path #{'hidable' if hidable}") do
+      output =
+        if show_new_nav?
+          image_tag(group_icon(group), class: "avatar-tile", width: 16, height: 16)
+        else
+          ""
+        end
+
+      output << simple_sanitize(group.name)
+      output.html_safe
+    end
+  end
 end
diff --git a/app/helpers/issuables_helper.rb b/app/helpers/issuables_helper.rb
index 3259a9c1933f2a3514a47c2fecbe26b592648f61..05177e58c5af3dfcd5e21cf78d18e79ed15de396 100644
--- a/app/helpers/issuables_helper.rb
+++ b/app/helpers/issuables_helper.rb
@@ -165,11 +165,7 @@ module IssuablesHelper
     }
 
     state_title = titles[state] || state.to_s.humanize
-
-    count =
-      Rails.cache.fetch(issuables_state_counter_cache_key(issuable_type, state), expires_in: 2.minutes) do
-        issuables_count_for_state(issuable_type, state)
-      end
+    count = issuables_count_for_state(issuable_type, state)
 
     html = content_tag(:span, state_title)
     html << " " << content_tag(:span, number_with_delimiter(count), class: 'badge')
@@ -237,6 +233,18 @@ module IssuablesHelper
     }
   end
 
+  def issuables_count_for_state(issuable_type, state, finder: nil)
+    finder ||= public_send("#{issuable_type}_finder")
+    cache_key = finder.state_counter_cache_key(state)
+
+    @counts ||= {}
+    @counts[cache_key] ||= Rails.cache.fetch(cache_key, expires_in: 2.minutes) do
+      finder.count_by_state
+    end
+
+    @counts[cache_key][state]
+  end
+
   private
 
   def sidebar_gutter_collapsed?
@@ -255,24 +263,6 @@ module IssuablesHelper
     end
   end
 
-  def issuables_count_for_state(issuable_type, state)
-    @counts ||= {}
-    @counts[issuable_type] ||= public_send("#{issuable_type}_finder").count_by_state
-    @counts[issuable_type][state]
-  end
-
-  IRRELEVANT_PARAMS_FOR_CACHE_KEY = %i[utf8 sort page].freeze
-  private_constant :IRRELEVANT_PARAMS_FOR_CACHE_KEY
-
-  def issuables_state_counter_cache_key(issuable_type, state)
-    opts = params.with_indifferent_access
-    opts[:state] = state
-    opts.except!(*IRRELEVANT_PARAMS_FOR_CACHE_KEY)
-    opts.delete_if { |_, value| value.blank? }
-
-    hexdigest(['issuables_count', issuable_type, opts.sort].flatten.join('-'))
-  end
-
   def issuable_templates(issuable)
     @issuable_templates ||=
       case issuable
diff --git a/app/helpers/milestones_helper.rb b/app/helpers/milestones_helper.rb
index a230db22fa2490d61b08a70ad341a8c8ca0b479d..f346e20e8074fc93271ea4d655f5ae18802310ff 100644
--- a/app/helpers/milestones_helper.rb
+++ b/app/helpers/milestones_helper.rb
@@ -74,6 +74,8 @@ module MilestonesHelper
     project = @target_project || @project
     if project
       namespace_project_milestones_path(project.namespace, project, :json)
+    elsif @group
+      group_milestones_path(@group, :json)
     else
       dashboard_milestones_path(:json)
     end
diff --git a/app/helpers/notes_helper.rb b/app/helpers/notes_helper.rb
index 64ad7b280cb3f0fb5431b05853759096840f52a6..ecc6cd6c6c574bde63de5afe26fc87d3e6e819da 100644
--- a/app/helpers/notes_helper.rb
+++ b/app/helpers/notes_helper.rb
@@ -47,6 +47,18 @@ module NotesHelper
     data
   end
 
+  def add_diff_note_button(line_code, position, line_type)
+    return if @diff_notes_disabled
+
+    button_tag '',
+      class: 'add-diff-note js-add-diff-note-button',
+      type: 'submit', name: 'button',
+      data: diff_view_line_data(line_code, position, line_type),
+      title: 'Add a comment to this line' do
+      icon('comment-o')
+    end
+  end
+
   def link_to_reply_discussion(discussion, line_type = nil)
     return unless current_user
 
diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb
index c04b1419a19f9b33f71e1971b4b892560a3310f6..53d95c2de94bc6bfee93ce59f833eea9abaf6aee 100644
--- a/app/helpers/projects_helper.rb
+++ b/app/helpers/projects_helper.rb
@@ -58,7 +58,17 @@ module ProjectsHelper
         link_to(simple_sanitize(owner.name), user_path(owner))
       end
 
-    project_link = link_to simple_sanitize(project.name), project_path(project), { class: "project-item-select-holder" }
+    project_link = link_to project_path(project), { class: "project-item-select-holder" } do
+      output =
+        if show_new_nav?
+          project_icon(project, alt: project.name, class: 'avatar-tile', width: 16, height: 16)
+        else
+          ""
+        end
+
+      output << simple_sanitize(project.name)
+      output.html_safe
+    end
 
     if current_user
       project_link << button_tag(type: 'button', class: 'dropdown-toggle-caret js-projects-dropdown-toggle', aria: { label: 'Toggle switch project dropdown' }, data: { target: '.js-dropdown-menu-projects', toggle: 'dropdown', order_by: 'last_activity_at' }) do
diff --git a/app/helpers/webpack_helper.rb b/app/helpers/webpack_helper.rb
index 6bacda9fe75153913e25ea770a344247629eb3b1..0386df223741901d61aad057e660eb0f9b4d99f3 100644
--- a/app/helpers/webpack_helper.rb
+++ b/app/helpers/webpack_helper.rb
@@ -11,20 +11,29 @@ module WebpackHelper
 
     paths = Webpack::Rails::Manifest.asset_paths(source)
     if extension
-      paths = paths.select { |p| p.ends_with? ".#{extension}" }
+      paths.select! { |p| p.ends_with? ".#{extension}" }
     end
 
-    # include full webpack-dev-server url for rspec tests running locally
+    force_host = webpack_public_host
+    if force_host
+      paths.map! { |p| "#{force_host}#{p}" }
+    end
+
+    paths
+  end
+
+  def webpack_public_host
     if Rails.env.test? && Rails.configuration.webpack.dev_server.enabled
       host = Rails.configuration.webpack.dev_server.host
       port = Rails.configuration.webpack.dev_server.port
       protocol = Rails.configuration.webpack.dev_server.https ? 'https' : 'http'
-
-      paths.map! do |p|
-        "#{protocol}://#{host}:#{port}#{p}"
-      end
+      "#{protocol}://#{host}:#{port}"
+    else
+      ActionController::Base.asset_host.try(:chomp, '/')
     end
+  end
 
-    paths
+  def webpack_public_path
+    "#{webpack_public_host}/#{Rails.application.config.webpack.public_path}/"
   end
 end
diff --git a/app/models/ability.rb b/app/models/ability.rb
index d2b8a8447b5d43f4768ed9d2303188d9fd36a1d3..0b6bcbde5d94aa639a3a147d923d741eb2748eca 100644
--- a/app/models/ability.rb
+++ b/app/models/ability.rb
@@ -1,4 +1,4 @@
-require 'declarative_policy'
+require_dependency 'declarative_policy'
 
 class Ability
   class << self
diff --git a/app/models/concerns/feature_gate.rb b/app/models/concerns/feature_gate.rb
new file mode 100644
index 0000000000000000000000000000000000000000..5db64fe82c4a3e4e9a213e57f9781bb62589df82
--- /dev/null
+++ b/app/models/concerns/feature_gate.rb
@@ -0,0 +1,7 @@
+module FeatureGate
+  def flipper_id
+    return nil if new_record?
+
+    "#{self.class.name}:#{id}"
+  end
+end
diff --git a/app/models/concerns/mentionable/reference_regexes.rb b/app/models/concerns/mentionable/reference_regexes.rb
index 1848230ec7e0b4f15993958f641082249230e617..2d86a70c395e5a40328f5eb80376aabfc425d4f2 100644
--- a/app/models/concerns/mentionable/reference_regexes.rb
+++ b/app/models/concerns/mentionable/reference_regexes.rb
@@ -14,7 +14,7 @@ module Mentionable
     end
 
     EXTERNAL_PATTERN = begin
-      issue_pattern = ExternalIssue.reference_pattern
+      issue_pattern = IssueTrackerService.reference_pattern
       link_patterns = URI.regexp(%w(http https))
       reference_pattern(link_patterns, issue_pattern)
     end
diff --git a/app/models/concerns/routable.rb b/app/models/concerns/routable.rb
index ec7796a9dbb0f0b245bc4d920f9e8da3a29f0dca..ee108f010a69052cee07c6fd41cf81ac48caffbb 100644
--- a/app/models/concerns/routable.rb
+++ b/app/models/concerns/routable.rb
@@ -103,8 +103,12 @@ module Routable
   def full_path
     return uncached_full_path unless RequestStore.active?
 
-    key = "routable/full_path/#{self.class.name}/#{self.id}"
-    RequestStore[key] ||= uncached_full_path
+    RequestStore[full_path_key] ||= uncached_full_path
+  end
+
+  def expires_full_path_cache
+    RequestStore.delete(full_path_key) if RequestStore.active?
+    @full_path = nil
   end
 
   def build_full_path
@@ -135,6 +139,10 @@ module Routable
     path_changed? || parent_changed?
   end
 
+  def full_path_key
+    @full_path_key ||= "routable/full_path/#{self.class.name}/#{self.id}"
+  end
+
   def build_full_name
     if parent && name
       parent.human_name + ' / ' + name
diff --git a/app/models/concerns/sha_attribute.rb b/app/models/concerns/sha_attribute.rb
new file mode 100644
index 0000000000000000000000000000000000000000..c28974a3cdfed941bb940f23743ad45e67e02dd1
--- /dev/null
+++ b/app/models/concerns/sha_attribute.rb
@@ -0,0 +1,18 @@
+module ShaAttribute
+  extend ActiveSupport::Concern
+
+  module ClassMethods
+    def sha_attribute(name)
+      column = columns.find { |c| c.name == name.to_s }
+
+      # In case the table doesn't exist we won't be able to find the column,
+      # thus we will only check the type if the column is present.
+      if column && column.type != :binary
+        raise ArgumentError,
+          "sha_attribute #{name.inspect} is invalid since the column type is not :binary"
+      end
+
+      attribute(name, Gitlab::Database::ShaAttribute.new)
+    end
+  end
+end
diff --git a/app/models/concerns/sortable.rb b/app/models/concerns/sortable.rb
index fdacfa5a194fe8242f4f57df5df2ba196eaba34b..a155a0640321f033b666b55b92a9f0ed84a9f05a 100644
--- a/app/models/concerns/sortable.rb
+++ b/app/models/concerns/sortable.rb
@@ -5,25 +5,6 @@
 module Sortable
   extend ActiveSupport::Concern
 
-  module DropDefaultScopeOnFinders
-    # Override these methods to drop the `ORDER BY id DESC` default scope.
-    # See http://dba.stackexchange.com/a/110919 for why we do this.
-    %i[find find_by find_by!].each do |meth|
-      define_method meth do |*args, &block|
-        return super(*args, &block) if block
-
-        unordered_relation = unscope(:order)
-
-        # We cannot simply call `meth` on `unscope(:order)`, since that is also
-        # an instance of the same relation class this module is included into,
-        # which means we'd get infinite recursion.
-        # We explicitly use the original implementation to prevent this.
-        original_impl = method(__method__).super_method.unbind
-        original_impl.bind(unordered_relation).call(*args)
-      end
-    end
-  end
-
   included do
     # By default all models should be ordered
     # by created_at field starting from newest
@@ -37,10 +18,6 @@ module Sortable
     scope :order_updated_asc, -> { reorder(updated_at: :asc) }
     scope :order_name_asc, -> { reorder(name: :asc) }
     scope :order_name_desc, -> { reorder(name: :desc) }
-
-    # All queries (relations) on this model are instances of this `relation_klass`.
-    relation_klass = relation_delegate_class(ActiveRecord::Relation)
-    relation_klass.prepend DropDefaultScopeOnFinders
   end
 
   module ClassMethods
diff --git a/app/models/external_issue.rb b/app/models/external_issue.rb
index e63f89a9f851abf369a345d5b15b500f9a1a6926..0bf18e529f029e5761290a711194bc04544fe4e4 100644
--- a/app/models/external_issue.rb
+++ b/app/models/external_issue.rb
@@ -38,11 +38,6 @@ class ExternalIssue
     @project.id
   end
 
-  # Pattern used to extract `JIRA-123` issue references from text
-  def self.reference_pattern
-    @reference_pattern ||= %r{(?<issue>\b([A-Z][A-Z0-9_]+-)\d+)}
-  end
-
   def to_reference(_from_project = nil, full: nil)
     id
   end
diff --git a/app/models/namespace.rb b/app/models/namespace.rb
index efbed5a2ef561970de08627aadd46d62d7a0c9c8..672eab94c07ff54ffa4bf57c93dac8c6c82473df 100644
--- a/app/models/namespace.rb
+++ b/app/models/namespace.rb
@@ -1,5 +1,5 @@
 class Namespace < ActiveRecord::Base
-  acts_as_paranoid
+  acts_as_paranoid without_default_scope: true
 
   include CacheMarkdownField
   include Sortable
@@ -47,8 +47,6 @@ class Namespace < ActiveRecord::Base
   before_destroy(prepend: true) { prepare_for_destroy }
   after_destroy :rm_dir
 
-  default_scope { with_deleted }
-
   scope :for_user, -> { where('type IS NULL') }
 
   scope :with_statistics, -> do
@@ -221,6 +219,12 @@ class Namespace < ActiveRecord::Base
     parent.present?
   end
 
+  def soft_delete_without_removing_associations
+    # We can't use paranoia's `#destroy` since this will hard-delete projects.
+    # Project uses `pending_delete` instead of the acts_as_paranoia gem.
+    self.deleted_at = Time.now
+  end
+
   private
 
   def repository_storage_paths
diff --git a/app/models/project.rb b/app/models/project.rb
index 228f66b95cd5a2fd8a834bb17a949df43f056592..a6708cf48ac6622f7b8a044b23f2a84d449193de 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -727,8 +727,8 @@ class Project < ActiveRecord::Base
     end
   end
 
-  def issue_reference_pattern
-    issues_tracker.reference_pattern
+  def external_issue_reference_pattern
+    external_issue_tracker.class.reference_pattern
   end
 
   def default_issues_tracker?
@@ -815,7 +815,7 @@ class Project < ActiveRecord::Base
   end
 
   def ci_service
-    @ci_service ||= ci_services.find_by(active: true)
+    @ci_service ||= ci_services.reorder(nil).find_by(active: true)
   end
 
   def deployment_services
@@ -823,7 +823,7 @@ class Project < ActiveRecord::Base
   end
 
   def deployment_service
-    @deployment_service ||= deployment_services.find_by(active: true)
+    @deployment_service ||= deployment_services.reorder(nil).find_by(active: true)
   end
 
   def monitoring_services
@@ -831,7 +831,7 @@ class Project < ActiveRecord::Base
   end
 
   def monitoring_service
-    @monitoring_service ||= monitoring_services.find_by(active: true)
+    @monitoring_service ||= monitoring_services.reorder(nil).find_by(active: true)
   end
 
   def jira_tracker?
@@ -963,6 +963,7 @@ class Project < ActiveRecord::Base
       begin
         gitlab_shell.mv_repository(repository_storage_path, "#{old_path_with_namespace}.wiki", "#{new_path_with_namespace}.wiki")
         send_move_instructions(old_path_with_namespace)
+        expires_full_path_cache
 
         @old_path_with_namespace = old_path_with_namespace
 
diff --git a/app/models/project_services/issue_tracker_service.rb b/app/models/project_services/issue_tracker_service.rb
index ff138b9066dced98a1a8884c9d82576a8d51f14f..fcc7c4bec06eeee03952829cdb16f34d2e4583b1 100644
--- a/app/models/project_services/issue_tracker_service.rb
+++ b/app/models/project_services/issue_tracker_service.rb
@@ -5,7 +5,10 @@ class IssueTrackerService < Service
 
   # Pattern used to extract links from comments
   # Override this method on services that uses different patterns
-  def reference_pattern
+  # This pattern does not support cross-project references
+  # The other code assumes that this pattern is a superset of all
+  # overriden patterns. See ReferenceRegexes::EXTERNAL_PATTERN
+  def self.reference_pattern
     @reference_pattern ||= %r{(\b[A-Z][A-Z0-9_]+-|#{Issue.reference_prefix})(?<issue>\d+)}
   end
 
diff --git a/app/models/project_services/jira_service.rb b/app/models/project_services/jira_service.rb
index 2450fb43212e91a669afbdc1f7a91f108d9e4551..00328892b4abf2ebdfc6947111f172e4207399ae 100644
--- a/app/models/project_services/jira_service.rb
+++ b/app/models/project_services/jira_service.rb
@@ -18,7 +18,7 @@ class JiraService < IssueTrackerService
   end
 
   # {PROJECT-KEY}-{NUMBER} Examples: JIRA-1, PROJECT-1
-  def reference_pattern
+  def self.reference_pattern
     @reference_pattern ||= %r{(?<issue>\b([A-Z][A-Z0-9_]+-)\d+)}
   end
 
diff --git a/app/models/user.rb b/app/models/user.rb
index 35e0d021c47fc4bb4bdccc828e4a14f2e1ad3eda..0febae84873bf0e73fc32a2c6a7066c198df8f3b 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -11,6 +11,7 @@ class User < ActiveRecord::Base
   include CaseSensitivity
   include TokenAuthenticatable
   include IgnorableColumn
+  include FeatureGate
 
   DEFAULT_NOTIFICATION_LEVEL = :participating
 
diff --git a/app/policies/base_policy.rb b/app/policies/base_policy.rb
index 00067ce756e455d2b36d0b6d28f5d8e88bc8b83e..191c2e78a087b2e6dc0c077893be4355f0f11482 100644
--- a/app/policies/base_policy.rb
+++ b/app/policies/base_policy.rb
@@ -1,4 +1,4 @@
-require 'declarative_policy'
+require_dependency 'declarative_policy'
 
 class BasePolicy < DeclarativePolicy::Base
   desc "User is an instance admin"
diff --git a/app/services/git_hooks_service.rb b/app/services/git_hooks_service.rb
index d222d1e63aa766113422ab087abfd02d0300cf9a..eab65d092996c668400843c45ca240ba09a846ad 100644
--- a/app/services/git_hooks_service.rb
+++ b/app/services/git_hooks_service.rb
@@ -3,8 +3,8 @@ class GitHooksService
 
   attr_accessor :oldrev, :newrev, :ref
 
-  def execute(user, repo_path, oldrev, newrev, ref)
-    @repo_path  = repo_path
+  def execute(user, project, oldrev, newrev, ref)
+    @project    = project
     @user       = Gitlab::GlId.gl_id(user)
     @oldrev     = oldrev
     @newrev     = newrev
@@ -26,7 +26,7 @@ class GitHooksService
   private
 
   def run_hook(name)
-    hook = Gitlab::Git::Hook.new(name, @repo_path)
+    hook = Gitlab::Git::Hook.new(name, @project)
     hook.trigger(@user, oldrev, newrev, ref)
   end
 end
diff --git a/app/services/git_operation_service.rb b/app/services/git_operation_service.rb
index ed6ea638235cb4c84f40e66383afc935d34a12ff..43636fde0be249fe4640d71e81d9706496445d55 100644
--- a/app/services/git_operation_service.rb
+++ b/app/services/git_operation_service.rb
@@ -120,7 +120,7 @@ class GitOperationService
   def with_hooks(ref, newrev, oldrev)
     GitHooksService.new.execute(
       user,
-      repository.path_to_repo,
+      repository.project,
       oldrev,
       newrev,
       ref) do |service|
diff --git a/app/services/groups/destroy_service.rb b/app/services/groups/destroy_service.rb
index d40d280140a2d1b8e6aba488292817291190c4fc..80c51cb5a7296669c0f3af827200b13d68999cff 100644
--- a/app/services/groups/destroy_service.rb
+++ b/app/services/groups/destroy_service.rb
@@ -1,8 +1,7 @@
 module Groups
   class DestroyService < Groups::BaseService
     def async_execute
-      # Soft delete via paranoia gem
-      group.destroy
+      group.soft_delete_without_removing_associations
       job_id = GroupDestroyWorker.perform_async(group.id, current_user.id)
       Rails.logger.info("User #{current_user.id} scheduled a deletion of group ID #{group.id} with job ID #{job_id}")
     end
diff --git a/app/services/projects/transfer_service.rb b/app/services/projects/transfer_service.rb
index fd701e335240bfa7c692b44f32be3ab469d9ed61..4bb98e5cb4ed0e1562fb804c2857812cb5e67c0d 100644
--- a/app/services/projects/transfer_service.rb
+++ b/app/services/projects/transfer_service.rb
@@ -78,6 +78,7 @@ module Projects
         Gitlab::PagesTransfer.new.move_project(project.path, @old_namespace.full_path, @new_namespace.full_path)
 
         project.old_path_with_namespace = @old_path
+        project.expires_full_path_cache
 
         execute_system_hooks
       end
diff --git a/app/services/projects/update_pages_service.rb b/app/services/projects/update_pages_service.rb
index 17cf71cf098e19fb8ef5bf0f5062b0e6833b1925..e60b854f916c109284c6f16b232c49b0f58fef67 100644
--- a/app/services/projects/update_pages_service.rb
+++ b/app/services/projects/update_pages_service.rb
@@ -93,10 +93,11 @@ module Projects
       end
 
       # Requires UnZip at least 6.00 Info-ZIP.
+      # -qq be (very) quiet
       # -n  never overwrite existing files
       # We add * to end of SITE_PATH, because we want to extract SITE_PATH and all subdirectories
       site_path = File.join(SITE_PATH, '*')
-      unless system(*%W(unzip -n #{artifacts} #{site_path} -d #{temp_path}))
+      unless system(*%W(unzip -qq -n #{artifacts} #{site_path} -d #{temp_path}))
         raise 'pages failed to extract'
       end
     end
diff --git a/app/views/dashboard/activity.html.haml b/app/views/dashboard/activity.html.haml
index f893c3e1675604e3d15ce1d83fb76d8e6e697c1a..ad35d05c29afcfa9ab053a2bb8db4f5f9fe413bc 100644
--- a/app/views/dashboard/activity.html.haml
+++ b/app/views/dashboard/activity.html.haml
@@ -1,3 +1,4 @@
+- @hide_top_links = true
 - @no_container = true
 
 = content_for :meta_tags do
diff --git a/app/views/dashboard/groups/index.html.haml b/app/views/dashboard/groups/index.html.haml
index f9b45a539a119c2f6fb471b6426f93d726d53973..1cea81827330dd0cc687abf434583b66e80a0948 100644
--- a/app/views/dashboard/groups/index.html.haml
+++ b/app/views/dashboard/groups/index.html.haml
@@ -1,3 +1,4 @@
+- @hide_top_links = true
 - page_title "Groups"
 - header_title  "Groups", dashboard_groups_path
 = render 'dashboard/groups_head'
diff --git a/app/views/dashboard/milestones/index.html.haml b/app/views/dashboard/milestones/index.html.haml
index 664ec618b7965375fea11b547dad4055faeaf0f5..ef1467c4d78538614a7b96078aa8fcc6c10a7960 100644
--- a/app/views/dashboard/milestones/index.html.haml
+++ b/app/views/dashboard/milestones/index.html.haml
@@ -1,3 +1,4 @@
+- @hide_top_links = true
 - page_title 'Milestones'
 - header_title 'Milestones', dashboard_milestones_path
 
diff --git a/app/views/dashboard/projects/index.html.haml b/app/views/dashboard/projects/index.html.haml
index 5e63a61e21b21062888fba6c9e016e0932a93b4c..7ac6cf06fb9e700554c547fdc2add478396c432c 100644
--- a/app/views/dashboard/projects/index.html.haml
+++ b/app/views/dashboard/projects/index.html.haml
@@ -1,4 +1,6 @@
 - @no_container = true
+- @hide_top_links = true
+- @breadcrumb_title = "Projects"
 
 = content_for :meta_tags do
   = auto_discovery_link_tag(:atom, dashboard_projects_url(rss_url_options), title: "All activity")
diff --git a/app/views/dashboard/snippets/index.html.haml b/app/views/dashboard/snippets/index.html.haml
index 85cbe0bf0e6e7a6e754c8a0b9413c6be95c1ab7b..e86b1ab311661ab69cde3562fdfde893e697cf71 100644
--- a/app/views/dashboard/snippets/index.html.haml
+++ b/app/views/dashboard/snippets/index.html.haml
@@ -1,3 +1,4 @@
+- @hide_top_links = true
 - page_title    "Snippets"
 - header_title  "Snippets", dashboard_snippets_path
 
diff --git a/app/views/devise/shared/_signup_box.html.haml b/app/views/devise/shared/_signup_box.html.haml
index d696577278d155a8c00f940a14f1f90435493bb2..298604dee8ca200ca8edf28596d4ea9a8457a0c9 100644
--- a/app/views/devise/shared/_signup_box.html.haml
+++ b/app/views/devise/shared/_signup_box.html.haml
@@ -25,7 +25,7 @@
       %div
       - if Gitlab::Recaptcha.enabled?
         = recaptcha_tags
-      %div
+      .submit-container
         = f.submit "Register", class: "btn-register btn"
 .clearfix.submit-container
   %p
diff --git a/app/views/layouts/_head.html.haml b/app/views/layouts/_head.html.haml
index cc710f4ec7d9709d5de114c30c05f8a4c3b4c73b..abb6dc2e9f300f96009bda9ef370e7f15e840be7 100644
--- a/app/views/layouts/_head.html.haml
+++ b/app/views/layouts/_head.html.haml
@@ -38,7 +38,7 @@
 
   = Gon::Base.render_data
 
-  = webpack_bundle_tag "runtime"
+  = webpack_bundle_tag "webpack_runtime"
   = webpack_bundle_tag "common"
   = webpack_bundle_tag "locale"
   = webpack_bundle_tag "main"
diff --git a/app/views/layouts/_page.html.haml b/app/views/layouts/_page.html.haml
index 62a76a1b00e56002a968bb78dd5a1e4628e62f0b..1a9f5401a78a9e16e5e0221e803ba23e30f6a47c 100644
--- a/app/views/layouts/_page.html.haml
+++ b/app/views/layouts/_page.html.haml
@@ -14,6 +14,8 @@
       = render "layouts/broadcast"
       = render "layouts/flash"
       = yield :flash_message
+    - if show_new_nav?
+      = render "layouts/nav/breadcrumbs"
     %div{ class: "#{(container_class unless @no_container)} #{@content_class}" }
       .content{ id: "content-body" }
         = yield
diff --git a/app/views/layouts/header/_default.html.haml b/app/views/layouts/header/_default.html.haml
index f056c0af968bec0d267e43969e844a10726c6855..8cbc3f6105f422dbf42309eae410ee20815425da 100644
--- a/app/views/layouts/header/_default.html.haml
+++ b/app/views/layouts/header/_default.html.haml
@@ -17,7 +17,7 @@
         = link_to root_path, class: 'home', title: 'Dashboard', id: 'logo' do
           = brand_header_logo
 
-      .title-container
+      .title-container.js-title-container
         %h1.title{ class: ('initializing' if @has_group_title) }= title
 
       .navbar-collapse.collapse
diff --git a/app/views/layouts/header/_new.html.haml b/app/views/layouts/header/_new.html.haml
index c0833c6491121dc4c71ba223038ae4ca12e66934..5859f689dd10c2596e89d7098ab99f596a77c1b9 100644
--- a/app/views/layouts/header/_new.html.haml
+++ b/app/views/layouts/header/_new.html.haml
@@ -83,8 +83,6 @@
         = icon('ellipsis-v', class: 'js-navbar-toggle-right')
         = icon('times', class: 'js-navbar-toggle-left', style: 'display: none;')
 
-      = yield :header_content
-
 = render 'shared/outdated_browser'
 
 - if @project && !@project.empty_repo?
diff --git a/app/views/layouts/nav/_breadcrumbs.html.haml b/app/views/layouts/nav/_breadcrumbs.html.haml
new file mode 100644
index 0000000000000000000000000000000000000000..5f1641f43001c4e34e18cbeffa8c5f6179ef04d3
--- /dev/null
+++ b/app/views/layouts/nav/_breadcrumbs.html.haml
@@ -0,0 +1,19 @@
+- breadcrumb_title = @breadcrumb_title || controller.controller_name.humanize
+- hide_top_links = @hide_top_links || false
+
+%nav.breadcrumbs{ role: "navigation" }
+  .breadcrumbs-container{ class: container_class }
+    .breadcrumbs-links.js-title-container
+      - unless hide_top_links
+        .title
+          = link_to "GitLab", root_path
+          \/
+          = header_title
+      %h2.breadcrumbs-sub-title
+        %ul.list-unstyled
+          - if content_for?(:sub_title_before)
+            = yield :sub_title_before
+          %li= link_to breadcrumb_title, request.path
+    - if content_for?(:breadcrumbs_extra)
+      .breadcrumbs-extra.hidden-xs= yield :breadcrumbs_extra
+    = yield :header_content
diff --git a/app/views/layouts/nav/_project.html.haml b/app/views/layouts/nav/_project.html.haml
index 68024d782a6b2bbae1b95f293d17e3d99ee3fab8..b095adcfe7eb9eb41344a83da7d26d0e0cea59c3 100644
--- a/app/views/layouts/nav/_project.html.haml
+++ b/app/views/layouts/nav/_project.html.haml
@@ -28,7 +28,7 @@
           %span
             Issues
             - if @project.default_issues_tracker?
-              %span.badge.count.issue_counter= number_with_delimiter(IssuesFinder.new(current_user, project_id: @project.id).execute.opened.count)
+              %span.badge.count.issue_counter= number_with_delimiter(issuables_count_for_state(:issues, :opened, finder: IssuesFinder.new(current_user, project_id: @project.id)))
 
     - if project_nav_tab? :merge_requests
       - controllers = [:merge_requests, 'projects/merge_requests/conflicts']
@@ -37,7 +37,7 @@
         = link_to namespace_project_merge_requests_path(@project.namespace, @project), title: 'Merge Requests', class: 'shortcuts-merge_requests' do
           %span
             Merge Requests
-            %span.badge.count.merge_counter.js-merge-counter= number_with_delimiter(MergeRequestsFinder.new(current_user, project_id: @project.id).execute.opened.count)
+            %span.badge.count.merge_counter.js-merge-counter= number_with_delimiter(issuables_count_for_state(:merge_requests, :opened, finder: MergeRequestsFinder.new(current_user, project_id: @project.id)))
 
     - if project_nav_tab? :pipelines
       = nav_link(controller: [:pipelines, :builds, :environments, :artifacts]) do
diff --git a/app/views/projects/boards/_show.html.haml b/app/views/projects/boards/_show.html.haml
index 6684ecfce8106ff9f4ceb9a44755564b07e151bf..3622720a8b7c4db8080565aa62198ea3346c378e 100644
--- a/app/views/projects/boards/_show.html.haml
+++ b/app/views/projects/boards/_show.html.haml
@@ -2,6 +2,10 @@
 - @content_class = "issue-boards-content"
 - page_title "Boards"
 
+- if show_new_nav?
+  - content_for :sub_title_before do
+    %li= link_to "Issues", namespace_project_issues_path(@project.namespace, @project)
+
 - content_for :page_specific_javascripts do
   = webpack_bundle_tag 'common_vue'
   = webpack_bundle_tag 'filtered_search'
diff --git a/app/views/projects/boards/components/_sidebar.html.haml b/app/views/projects/boards/components/_sidebar.html.haml
index 24d76da6f0632a26c84d9b04f95752aec4ace5f1..09d70f658a3a9463d28c27b63c0ff20364ba23c9 100644
--- a/app/views/projects/boards/components/_sidebar.html.haml
+++ b/app/views/projects/boards/components/_sidebar.html.haml
@@ -23,4 +23,5 @@
           = render "projects/boards/components/sidebar/labels"
           = render "projects/boards/components/sidebar/notifications"
           %remove-btn{ ":issue" => "issue",
-            ":list" => "list" }
+            ":list" => "list",
+            "v-if" => "canRemove" }
diff --git a/app/views/projects/commits/show.html.haml b/app/views/projects/commits/show.html.haml
index fabd825aec8967a1d8ae552c18b23b19af83ce8c..7ed7e441344b65d2b237a992072c707840ccc10a 100644
--- a/app/views/projects/commits/show.html.haml
+++ b/app/views/projects/commits/show.html.haml
@@ -8,27 +8,28 @@
   = render "head"
 
 %div{ class: container_class }
-  .row-content-block.second-block.content-component-block.flex-container-block
-    .tree-ref-holder
-      = render 'shared/ref_switcher', destination: 'commits'
+  .tree-holder
+    .nav-block
+      .tree-ref-container
+        .tree-ref-holder
+          = render 'shared/ref_switcher', destination: 'commits'
+
+        %ul.breadcrumb.repo-breadcrumb
+          = commits_breadcrumbs
+      .tree-controls.hidden-xs.hidden-sm
+        - if @merge_request.present?
+          .control
+            = link_to _("View open merge request"), namespace_project_merge_request_path(@project.namespace, @project, @merge_request), class: 'btn'
+        - elsif create_mr_button?(@repository.root_ref, @ref)
+          .control
+            = link_to _("Create merge request"), create_mr_path(@repository.root_ref, @ref), class: 'btn btn-success'
 
-    %ul.breadcrumb.repo-breadcrumb
-      = commits_breadcrumbs
-
-    .block-controls.hidden-xs.hidden-sm
-      - if @merge_request.present?
         .control
-          = link_to _("View open merge request"), namespace_project_merge_request_path(@project.namespace, @project, @merge_request), class: 'btn'
-      - elsif create_mr_button?(@repository.root_ref, @ref)
+          = form_tag(namespace_project_commits_path(@project.namespace, @project, @id), method: :get, class: 'commits-search-form') do
+            = search_field_tag :search, params[:search], { placeholder: _('Filter by commit message'), id: 'commits-search', class: 'form-control search-text-input input-short', spellcheck: false }
         .control
-          = link_to _("Create merge request"), create_mr_path(@repository.root_ref, @ref), class: 'btn btn-success'
-
-      .control
-        = form_tag(namespace_project_commits_path(@project.namespace, @project, @id), method: :get, class: 'commits-search-form') do
-          = search_field_tag :search, params[:search], { placeholder: _('Filter by commit message'), id: 'commits-search', class: 'form-control search-text-input input-short', spellcheck: false }
-      .control
-        = link_to namespace_project_commits_path(@project.namespace, @project, @ref, rss_url_options), title: _("Commits feed"), class: 'btn' do
-          = icon("rss")
+          = link_to namespace_project_commits_path(@project.namespace, @project, @ref, rss_url_options), title: _("Commits feed"), class: 'btn' do
+            = icon("rss")
 
   %div{ id: dom_id(@project) }
     %ol#commits-list.list-unstyled.content_list
diff --git a/app/views/projects/diffs/_line.html.haml b/app/views/projects/diffs/_line.html.haml
index 43708d22a0ce9b984e22540978df323d62f4bf2a..cd0fb21f8a7539f09f7e7ecd370f8be875f636a2 100644
--- a/app/views/projects/diffs/_line.html.haml
+++ b/app/views/projects/diffs/_line.html.haml
@@ -19,6 +19,7 @@
       - if plain
         = link_text
       - else
+        = add_diff_note_button(line_code, diff_file.position(line), type)
         %a{ href: "##{line_code}", data: { linenumber: link_text } }
       - discussion = line_discussions.try(:first)
       - if discussion && discussion.resolvable? && !plain
@@ -29,7 +30,7 @@
         = link_text
       - else
         %a{ href: "##{line_code}", data: { linenumber: link_text } }
-    %td.line_content.noteable_line{ class: type, data: (diff_view_line_data(line_code, diff_file.position(line), type) unless plain) }<
+    %td.line_content.noteable_line{ class: type }<
       - if email
         %pre= line.text
       - else
diff --git a/app/views/projects/diffs/_parallel_view.html.haml b/app/views/projects/diffs/_parallel_view.html.haml
index 8e5f4d2573d65bc0444fe522410ee7be9e3ef436..56d63250714f303c14bace3e51fb59b63d363d7e 100644
--- a/app/views/projects/diffs/_parallel_view.html.haml
+++ b/app/views/projects/diffs/_parallel_view.html.haml
@@ -1,4 +1,5 @@
 / Side-by-side diff view
+
 .text-file.diff-wrap-lines.code.js-syntax-highlight{ data: diff_view_data }
   %table
     - diff_file.parallel_diff_lines.each do |line|
@@ -18,11 +19,12 @@
             - left_line_code = diff_file.line_code(left)
             - left_position = diff_file.position(left)
             %td.old_line.diff-line-num.js-avatar-container{ id: left_line_code, class: left.type, data: { linenumber: left.old_pos } }
+              = add_diff_note_button(left_line_code, left_position, 'old')
               %a{ href: "##{left_line_code}", data: { linenumber: left.old_pos } }
               - discussion_left = discussions_left.try(:first)
               - if discussion_left && discussion_left.resolvable?
                 %diff-note-avatars{ "discussion-id" => discussion_left.id }
-            %td.line_content.parallel.noteable_line{ class: left.type, data: diff_view_line_data(left_line_code, left_position, 'old') }= diff_line_content(left.text)
+            %td.line_content.parallel.noteable_line{ class: left.type }= diff_line_content(left.text)
         - else
           %td.old_line.diff-line-num.empty-cell
           %td.line_content.parallel
@@ -38,11 +40,12 @@
             - right_line_code = diff_file.line_code(right)
             - right_position = diff_file.position(right)
             %td.new_line.diff-line-num.js-avatar-container{ id: right_line_code, class: right.type, data: { linenumber: right.new_pos } }
+              = add_diff_note_button(right_line_code, right_position, 'new')
               %a{ href: "##{right_line_code}", data: { linenumber: right.new_pos } }
               - discussion_right = discussions_right.try(:first)
               - if discussion_right && discussion_right.resolvable?
                 %diff-note-avatars{ "discussion-id" => discussion_right.id }
-            %td.line_content.parallel.noteable_line{ class: right.type, data: diff_view_line_data(right_line_code, right_position, 'new') }= diff_line_content(right.text)
+            %td.line_content.parallel.noteable_line{ class: right.type }= diff_line_content(right.text)
         - else
           %td.old_line.diff-line-num.empty-cell
           %td.line_content.parallel
diff --git a/app/views/projects/issues/_issue.html.haml b/app/views/projects/issues/_issue.html.haml
index 9e4e6934ca978322ff3f93de0f1976e8cefbee3f..6a0d96f50cd74299422c853db07f31eb17b250f4 100644
--- a/app/views/projects/issues/_issue.html.haml
+++ b/app/views/projects/issues/_issue.html.haml
@@ -4,43 +4,49 @@
       .issue-check.hidden
         = check_box_tag dom_id(issue, "selected"), nil, false, 'data-id' => issue.id, class: "selected_issue"
     .issue-info-container
-      .issue-title.title
-        %span.issue-title-text
-          = confidential_icon(issue)
-          = link_to issue.title, issue_path(issue)
+      .issue-main-info
+        .issue-title.title
+          %span.issue-title-text
+            = confidential_icon(issue)
+            = link_to issue.title, issue_path(issue)
+          - if issue.tasks?
+            %span.task-status.hidden-xs
+              &nbsp;
+              = issue.task_status
+
+        .issuable-info
+          %span.issuable-reference
+            #{issuable_reference(issue)}
+          %span.issuable-authored.hidden-xs
+            &middot;
+            opened #{time_ago_with_tooltip(issue.created_at, placement: 'bottom')}
+            by #{link_to_member(@project, issue.author, avatar: false)}
+          - if issue.milestone
+            %span.issuable-milestone.hidden-xs
+              &nbsp;
+              = link_to namespace_project_issues_path(issue.project.namespace, issue.project, milestone_title: issue.milestone.title) do
+                = icon('clock-o')
+                = issue.milestone.title
+          - if issue.due_date
+            %span.issuable-due-date.hidden-xs{ class: "#{'cred' if issue.overdue?}" }
+              &nbsp;
+              = icon('calendar')
+              = issue.due_date.to_s(:medium)
+          - if issue.labels.any?
+            &nbsp;
+            - issue.labels.each do |label|
+              = link_to_label(label, subject: issue.project, css_class: 'label-link')
+
+      .issuable-meta
         %ul.controls
           - if issue.closed?
-            %li
+            %li.issuable-status
               CLOSED
-
           - if issue.assignees.any?
             %li
               = render 'shared/issuable/assignees', project: @project, issue: issue
 
           = render 'shared/issuable_meta_data', issuable: issue
 
-      .issue-info
-        #{issuable_reference(issue)} &middot;
-        opened #{time_ago_with_tooltip(issue.created_at, placement: 'bottom')}
-        by #{link_to_member(@project, issue.author, avatar: false)}
-        - if issue.milestone
-          &nbsp;
-          = link_to namespace_project_issues_path(issue.project.namespace, issue.project, milestone_title: issue.milestone.title) do
-            = icon('clock-o')
-            = issue.milestone.title
-        - if issue.due_date
-          %span{ class: "#{'cred' if issue.overdue?}" }
-            &nbsp;
-            = icon('calendar')
-            = issue.due_date.to_s(:medium)
-        - if issue.labels.any?
-          &nbsp;
-          - issue.labels.each do |label|
-            = link_to_label(label, subject: issue.project, css_class: 'label-link')
-        - if issue.tasks?
-          &nbsp;
-          %span.task-status
-            = issue.task_status
-
-        .pull-right.issue-updated-at
+        .pull-right.issuable-updated-at.hidden-xs
           %span updated #{time_ago_with_tooltip(issue.updated_at, placement: 'bottom', html_class: 'issue_update_ago')}
diff --git a/app/views/projects/issues/_nav_btns.html.haml b/app/views/projects/issues/_nav_btns.html.haml
new file mode 100644
index 0000000000000000000000000000000000000000..698959ec74ff2a5792702eafc64cdaf67e96ee7a
--- /dev/null
+++ b/app/views/projects/issues/_nav_btns.html.haml
@@ -0,0 +1,11 @@
+= link_to params.merge(rss_url_options), class: 'btn btn-default append-right-10 has-tooltip', title: 'Subscribe' do
+  = icon('rss')
+- if @can_bulk_update
+  = button_tag "Edit Issues", class: "btn btn-default append-right-10 js-bulk-update-toggle"
+= link_to "New issue", new_namespace_project_issue_path(@project.namespace,
+                                         @project,
+                                         issue: { assignee_id: issues_finder.assignee.try(:id),
+                                                  milestone_id: issues_finder.milestones.first.try(:id) }),
+                                         class: "btn btn-new",
+                                         title: "New issue",
+                                         id: "new_issue_link"
diff --git a/app/views/projects/issues/index.html.haml b/app/views/projects/issues/index.html.haml
index 7183794ce7258adeba718f9bc582973315453c84..89ac5ff7128b79f36240b2b7277a9043f3ab5dd2 100644
--- a/app/views/projects/issues/index.html.haml
+++ b/app/views/projects/issues/index.html.haml
@@ -13,23 +13,16 @@
 = content_for :meta_tags do
   = auto_discovery_link_tag(:atom, params.merge(rss_url_options), title: "#{@project.name} issues")
 
+- if show_new_nav?
+  - content_for :breadcrumbs_extra do
+    = render "projects/issues/nav_btns"
+
 - if project_issues(@project).exists?
   %div{ class: (container_class) }
     .top-area
       = render 'shared/issuable/nav', type: :issues
-      .nav-controls
-        = link_to params.merge(rss_url_options), class: 'btn append-right-10 has-tooltip', title: 'Subscribe' do
-          = icon('rss')
-        - if @can_bulk_update
-          = button_tag "Edit Issues", class: "btn btn-default js-bulk-update-toggle"
-        = link_to new_namespace_project_issue_path(@project.namespace,
-                                                   @project,
-                                                   issue: { assignee_id: issues_finder.assignee.try(:id),
-                                                            milestone_id: issues_finder.milestones.first.try(:id) }),
-                                                   class: "btn btn-new",
-                                                   title: "New issue",
-                                                   id: "new_issue_link" do
-          New issue
+      .nav-controls{ class: ("visible-xs" if show_new_nav?) }
+        = render "projects/issues/nav_btns"
     = render 'shared/issuable/search_bar', type: :issues
 
     - if @can_bulk_update
diff --git a/app/views/projects/merge_requests/_merge_request.html.haml b/app/views/projects/merge_requests/_merge_request.html.haml
index c13110deb16df48e7261e711ba6c7fe5e6493736..3599f2271b5c3adc5bd2568e96de534252d5d4ee 100644
--- a/app/views/projects/merge_requests/_merge_request.html.haml
+++ b/app/views/projects/merge_requests/_merge_request.html.haml
@@ -4,58 +4,60 @@
       = check_box_tag dom_id(merge_request, "selected"), nil, false, 'data-id' => merge_request.id, class: "selected_issue"
 
   .issue-info-container
-    .merge-request-title.title
-      %span.merge-request-title-text
-        = link_to merge_request.title, merge_request_path(merge_request)
+    .issue-main-info
+      .merge-request-title.title
+        %span.merge-request-title-text
+          = link_to merge_request.title, merge_request_path(merge_request)
+        - if merge_request.tasks?
+          %span.task-status.hidden-xs
+            &nbsp;
+            = merge_request.task_status
+
+      .issuable-info
+        %span.issuable-reference
+          #{issuable_reference(merge_request)}
+        %span.issuable-authored.hidden-xs
+          &middot;
+          opened #{time_ago_with_tooltip(merge_request.created_at, placement: 'bottom')}
+          by #{link_to_member(@project, merge_request.author, avatar: false)}
+        - if merge_request.milestone
+          %span.issuable-milestone.hidden-xs
+            &nbsp;
+            = link_to namespace_project_merge_requests_path(merge_request.project.namespace, merge_request.project, milestone_title: merge_request.milestone.title) do
+              = icon('clock-o')
+              = merge_request.milestone.title
+        - if merge_request.target_project.default_branch != merge_request.target_branch
+          %span.project-ref-path
+            &nbsp;
+            = link_to project_ref_path(merge_request.project, merge_request.target_branch), class: 'ref-name' do
+              = icon('code-fork')
+              = merge_request.target_branch
+        - if merge_request.labels.any?
+          &nbsp;
+          - merge_request.labels.each do |label|
+            = link_to_label(label, subject: merge_request.project, type: :merge_request, css_class: 'label-link')
+
+    .issuable-meta
       %ul.controls
         - if merge_request.merged?
-          %li
+          %li.issuable-status.hidden-xs
             MERGED
         - elsif merge_request.closed?
-          %li
+          %li.issuable-status.hidden-xs
             = icon('ban')
             CLOSED
-
         - if merge_request.head_pipeline
-          %li
+          %li.issuable-pipeline-status.hidden-xs
             = render_pipeline_status(merge_request.head_pipeline)
-
         - if merge_request.open? && merge_request.broken?
-          %li
+          %li.issuable-pipeline-broken.hidden-xs
             = link_to merge_request_path(merge_request), class: "has-tooltip", title: "Cannot be merged automatically", data: { container: 'body' } do
               = icon('exclamation-triangle')
-
         - if merge_request.assignee
           %li
             = link_to_member(merge_request.source_project, merge_request.assignee, name: false, title: "Assigned to :name")
 
         = render 'shared/issuable_meta_data', issuable: merge_request
 
-    .merge-request-info
-      #{issuable_reference(merge_request)} &middot;
-      opened #{time_ago_with_tooltip(merge_request.created_at, placement: 'bottom')}
-      by #{link_to_member(@project, merge_request.author, avatar: false)}
-      - if merge_request.target_project.default_branch != merge_request.target_branch
-        &nbsp;
-        = link_to project_ref_path(merge_request.project, merge_request.target_branch), class: 'ref-name' do
-          = icon('code-fork')
-          = merge_request.target_branch
-
-      - if merge_request.milestone
-        &nbsp;
-        = link_to namespace_project_merge_requests_path(merge_request.project.namespace, merge_request.project, milestone_title: merge_request.milestone.title) do
-          = icon('clock-o')
-          = merge_request.milestone.title
-
-      - if merge_request.labels.any?
-        &nbsp;
-        - merge_request.labels.each do |label|
-          = link_to_label(label, subject: merge_request.project, type: :merge_request, css_class: 'label-link')
-
-      - if merge_request.tasks?
-        &nbsp;
-        %span.task-status
-          = merge_request.task_status
-
-      .pull-right.hidden-xs
+      .pull-right.issuable-updated-at.hidden-xs
         %span updated #{time_ago_with_tooltip(merge_request.updated_at, placement: 'bottom', html_class: 'merge_request_updated_ago')}
diff --git a/app/views/projects/merge_requests/_nav_btns.html.haml b/app/views/projects/merge_requests/_nav_btns.html.haml
new file mode 100644
index 0000000000000000000000000000000000000000..e92f27123473a3dfeb07a7b5197b3342a2da57f8
--- /dev/null
+++ b/app/views/projects/merge_requests/_nav_btns.html.haml
@@ -0,0 +1,5 @@
+- if @can_bulk_update
+  = button_tag "Edit Merge Requests", class: "btn js-bulk-update-toggle"
+- if merge_project
+  = link_to new_merge_request_path, class: "btn btn-new", title: "New merge request" do
+    New merge request
diff --git a/app/views/projects/merge_requests/index.html.haml b/app/views/projects/merge_requests/index.html.haml
index 86996e488a17f87bf80c06657b7e31e14641734e..6fe44ba3c3d6add353b79e21ffedee66c60ae72b 100644
--- a/app/views/projects/merge_requests/index.html.haml
+++ b/app/views/projects/merge_requests/index.html.haml
@@ -1,5 +1,7 @@
 - @no_container = true
 - @can_bulk_update = can?(current_user, :admin_merge_request, @project)
+- merge_project = can?(current_user, :create_merge_request, @project) ? @project : (current_user && current_user.fork_of(@project))
+- new_merge_request_path = namespace_project_new_merge_request_path(merge_project.namespace, merge_project) if merge_project
 
 - page_title "Merge Requests"
 - unless @project.default_issues_tracker?
@@ -10,6 +12,9 @@
   = webpack_bundle_tag 'common_vue'
   = webpack_bundle_tag 'filtered_search'
 
+- if show_new_nav?
+  - content_for :breadcrumbs_extra do
+    = render "projects/merge_requests/nav_btns", merge_project: merge_project, new_merge_request_path: new_merge_request_path
 
 = render 'projects/last_push'
 
@@ -17,13 +22,8 @@
   %div{ class: container_class }
     .top-area
       = render 'shared/issuable/nav', type: :merge_requests
-      .nav-controls
-        - if @can_bulk_update
-          = button_tag "Edit Merge Requests", class: "btn js-bulk-update-toggle"
-        - merge_project = can?(current_user, :create_merge_request, @project) ? @project : (current_user && current_user.fork_of(@project))
-        - if merge_project
-          = link_to namespace_project_new_merge_request_path(merge_project.namespace, merge_project), class: "btn btn-new", title: "New merge request" do
-            New merge request
+      .nav-controls{ class: ("visible-xs" if show_new_nav?) }
+        = render "projects/merge_requests/nav_btns", merge_project: merge_project, new_merge_request_path: new_merge_request_path
 
     = render 'shared/issuable/search_bar', type: :merge_requests
 
@@ -33,4 +33,4 @@
     .merge-requests-holder
       = render 'merge_requests'
 - else
-  = render 'shared/empty_states/merge_requests', button_path: namespace_project_new_merge_request_path(@project.namespace, @project)
+  = render 'shared/empty_states/merge_requests', button_path: new_merge_request_path
diff --git a/app/views/projects/show.html.haml b/app/views/projects/show.html.haml
index e1e70a53709ce6632bdd42eadf315546e7e43df0..152e50a79bbe5c47a8cd36c8e129e11b20bd1aab 100644
--- a/app/views/projects/show.html.haml
+++ b/app/views/projects/show.html.haml
@@ -73,7 +73,7 @@
             = link_to add_special_file_path(@project, file_name: '.gitlab-ci.yml', commit_message: 'Set up auto deploy', branch_name: 'auto-deploy', context: 'autodeploy') do
               #{ _('Set up auto deploy') }
 
-%div{ class: container_class }
+%div{ class: [container_class, ("limit-container-width" unless fluid_layout)] }
   - if @project.archived?
     .text-warning.center.prepend-top-20
       %p
diff --git a/app/views/shared/_issuable_meta_data.html.haml b/app/views/shared/_issuable_meta_data.html.haml
index 1d4fd71522d4628d96ab2f65ba4cba0c5f61fb64..435acbc634cabd37d9e695d47d0ad036fead6bed 100644
--- a/app/views/shared/_issuable_meta_data.html.haml
+++ b/app/views/shared/_issuable_meta_data.html.haml
@@ -5,21 +5,21 @@
 - issuable_mr        = @issuable_meta_data[issuable.id].merge_requests_count
 
 - if issuable_mr > 0
-  %li
+  %li.issuable-mr.hidden-xs
     = image_tag('icon-merge-request-unmerged.svg', class: 'icon-merge-request-unmerged')
     = issuable_mr
 
 - if upvotes > 0
-  %li
+  %li.issuable-upvotes.hidden-xs
     = icon('thumbs-up')
     = upvotes
 
 - if downvotes > 0
-  %li
+  %li.issuable-downvotes.hidden-xs
     = icon('thumbs-down')
     = downvotes
 
-%li
+%li.issuable-comments.hidden-xs
   = link_to issuable_url, class: ('no-comments' if note_count.zero?) do
     = icon('comments')
     = note_count
diff --git a/app/views/shared/_issues.html.haml b/app/views/shared/_issues.html.haml
index 3a49227961f4ce30d9d9af5a6f24ac6b64dfc26e..49555b6ff4e464c015d3353e181a496aded78432 100644
--- a/app/views/shared/_issues.html.haml
+++ b/app/views/shared/_issues.html.haml
@@ -1,6 +1,6 @@
 - if @issues.to_a.any?
   .panel.panel-default.panel-small.panel-without-border
-    %ul.content-list.issues-list
+    %ul.content-list.issues-list.issuable-list
       = render partial: 'projects/issues/issue', collection: @issues
   = paginate @issues, theme: "gitlab"
 - else
diff --git a/app/views/shared/_merge_requests.html.haml b/app/views/shared/_merge_requests.html.haml
index eecbb32e90e3e0856b30d263be0460953297bd4b..0517896cfbd7ece477764e29ef2e311bf8da0b22 100644
--- a/app/views/shared/_merge_requests.html.haml
+++ b/app/views/shared/_merge_requests.html.haml
@@ -1,6 +1,6 @@
 - if @merge_requests.to_a.any?
   .panel.panel-default.panel-small.panel-without-border
-    %ul.content-list.mr-list
+    %ul.content-list.mr-list.issuable-list
       = render partial: 'projects/merge_requests/merge_request', collection: @merge_requests
 
   = paginate @merge_requests, theme: "gitlab"
diff --git a/app/views/shared/_sort_dropdown.html.haml b/app/views/shared/_sort_dropdown.html.haml
index a212c714826d4bc2160e85e2ea0337a9bc2bbeeb..785a500e44ed0c334d053bcc14d7039c3d2e732a 100644
--- a/app/views/shared/_sort_dropdown.html.haml
+++ b/app/views/shared/_sort_dropdown.html.haml
@@ -1,3 +1,5 @@
+- viewing_issues = controller.controller_name == 'issues' || controller.action_name == 'issues'
+
 .dropdown.inline.prepend-left-10
   %button.dropdown-toggle{ type: 'button', data: {toggle: 'dropdown' } }
     - if @sort.present?
@@ -23,7 +25,7 @@
         = sort_title_milestone_soon
       = link_to page_filter_path(sort: sort_value_milestone_later, label: true) do
         = sort_title_milestone_later
-      - if controller.controller_name == 'issues' || controller.action_name == 'issues'
+      - if viewing_issues
         = link_to page_filter_path(sort: sort_value_due_date_soon, label: true) do
           = sort_title_due_date_soon
         = link_to page_filter_path(sort: sort_value_due_date_later, label: true) do
diff --git a/app/views/shared/members/_access_request_buttons.html.haml b/app/views/shared/members/_access_request_buttons.html.haml
index d97fdf179d7f9ae599463a61fa391c1c88e51d74..40224cec9e8bebd35bdd28fd27a852dfe7bb5081 100644
--- a/app/views/shared/members/_access_request_buttons.html.haml
+++ b/app/views/shared/members/_access_request_buttons.html.haml
@@ -1,18 +1,20 @@
 - model_name = source.model_name.to_s.downcase
 
-.project-action-button.inline
-  - if can?(current_user, :"destroy_#{model_name}_member", source.members.find_by(user_id: current_user.id))
+- if can?(current_user, :"destroy_#{model_name}_member", source.members.find_by(user_id: current_user.id))
+  .project-action-button.inline
     - link_text = source.is_a?(Group) ? _('Leave group') : _('Leave project')
     = link_to link_text, polymorphic_path([:leave, source, :members]),
               method: :delete,
               data: { confirm: leave_confirmation_message(source) },
               class: 'btn'
-  - elsif requester = source.requesters.find_by(user_id: current_user.id)
+- elsif requester = source.requesters.find_by(user_id: current_user.id)
+  .project-action-button.inline
     = link_to _('Withdraw Access Request'), polymorphic_path([:leave, source, :members]),
               method: :delete,
               data: { confirm: remove_member_message(requester) },
               class: 'btn'
-  - elsif source.request_access_enabled && can?(current_user, :request_access, source)
+- elsif source.request_access_enabled && can?(current_user, :request_access, source)
+  .project-action-button.inline
     = link_to _('Request Access'), polymorphic_path([:request_access, source, :members]),
               method: :post,
               class: 'btn'
diff --git a/changelogs/unreleased/23036-replace-dashboard-new-project-spinach.yml b/changelogs/unreleased/23036-replace-dashboard-new-project-spinach.yml
new file mode 100644
index 0000000000000000000000000000000000000000..a5f78202c932b5b1a16d7f316b8de56309dba12a
--- /dev/null
+++ b/changelogs/unreleased/23036-replace-dashboard-new-project-spinach.yml
@@ -0,0 +1,4 @@
+---
+title: Replace 'dashboard/new-project.feature' spinach with rspec
+merge_request: 12550
+author: Alexander Randa (@randaalex)
diff --git a/changelogs/unreleased/33443-supplement_traditional_chinese_in_taiwan_translation_of_i18n.yml b/changelogs/unreleased/33443-supplement_traditional_chinese_in_taiwan_translation_of_i18n.yml
new file mode 100644
index 0000000000000000000000000000000000000000..d6b1b2524c6284ec0cdef5e3393e252dd01b57c7
--- /dev/null
+++ b/changelogs/unreleased/33443-supplement_traditional_chinese_in_taiwan_translation_of_i18n.yml
@@ -0,0 +1,4 @@
+---
+title: Supplement Traditional Chinese in Taiwan translation of Project Page & Repository Page
+merge_request: 12514
+author: Huang Tao
diff --git a/changelogs/unreleased/34078-allow-to-enable-feature-flags-with-more-granularity.yml b/changelogs/unreleased/34078-allow-to-enable-feature-flags-with-more-granularity.yml
new file mode 100644
index 0000000000000000000000000000000000000000..69d5d34b07258b0d71f035cdeb69d3d45967d72b
--- /dev/null
+++ b/changelogs/unreleased/34078-allow-to-enable-feature-flags-with-more-granularity.yml
@@ -0,0 +1,4 @@
+---
+title: Allow the feature flags to be enabled/disabled with more granularity
+merge_request: 12357
+author:
diff --git a/changelogs/unreleased/34097-issue-board-remove-from-board-button-when-viewing-an-issue-gives-js-error-and-fails.yml b/changelogs/unreleased/34097-issue-board-remove-from-board-button-when-viewing-an-issue-gives-js-error-and-fails.yml
new file mode 100644
index 0000000000000000000000000000000000000000..c0ea75adfa08730fbadf2097413d96fd80fbf491
--- /dev/null
+++ b/changelogs/unreleased/34097-issue-board-remove-from-board-button-when-viewing-an-issue-gives-js-error-and-fails.yml
@@ -0,0 +1,4 @@
+---
+title: Remove "Remove from board" button from backlog and closed list
+merge_request: 12430
+author:
diff --git a/changelogs/unreleased/34116-milestone-filtering-on-group-issues.yml b/changelogs/unreleased/34116-milestone-filtering-on-group-issues.yml
new file mode 100644
index 0000000000000000000000000000000000000000..8f8b5a96c2b6577e9f293265a5aa73e9b5ae8782
--- /dev/null
+++ b/changelogs/unreleased/34116-milestone-filtering-on-group-issues.yml
@@ -0,0 +1,4 @@
+---
+title: Change milestone endpoint for groups
+merge_request: 12374
+author: Takuya Noguchi
diff --git a/changelogs/unreleased/34403-issue-dropdown-persists-when-adding-issue-number-to-issue-description.yml b/changelogs/unreleased/34403-issue-dropdown-persists-when-adding-issue-number-to-issue-description.yml
new file mode 100644
index 0000000000000000000000000000000000000000..4911315d018afaa6bd558787ce150e7b895d4053
--- /dev/null
+++ b/changelogs/unreleased/34403-issue-dropdown-persists-when-adding-issue-number-to-issue-description.yml
@@ -0,0 +1,4 @@
+---
+title: Closes any open Autocomplete of the markdown editor when the form is closed
+merge_request: 12521
+author: 
diff --git a/changelogs/unreleased/adam-external-issue-references-spike.yml b/changelogs/unreleased/adam-external-issue-references-spike.yml
new file mode 100644
index 0000000000000000000000000000000000000000..aeec66884259e242cf3d61c5b6a0a4b66d581e03
--- /dev/null
+++ b/changelogs/unreleased/adam-external-issue-references-spike.yml
@@ -0,0 +1,4 @@
+---
+title: Improve support for external issue references
+merge_request: 12485
+author:
diff --git a/changelogs/unreleased/dm-dependency-linker-newlines.yml b/changelogs/unreleased/dm-dependency-linker-newlines.yml
deleted file mode 100644
index 5631095fcb71c6fa4f31629c4af925fe6a577d1a..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/dm-dependency-linker-newlines.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fix diff of requirements.txt file by not matching newlines as part of package
-  names
-merge_request:
-author:
diff --git a/changelogs/unreleased/dm-drop-default-scope-on-sortable-finders.yml b/changelogs/unreleased/dm-drop-default-scope-on-sortable-finders.yml
deleted file mode 100644
index b359a25053a79498e5e7cee8b441774dfc52cacc..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/dm-drop-default-scope-on-sortable-finders.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Improve performance of lookups of issues, merge requests etc by dropping unnecessary ORDER BY clause
-merge_request:
-author:
diff --git a/changelogs/unreleased/dm-empty-state-new-merge-request.yml b/changelogs/unreleased/dm-empty-state-new-merge-request.yml
new file mode 100644
index 0000000000000000000000000000000000000000..5fad7a0f883dd296af38e7400b723a25d9568e0b
--- /dev/null
+++ b/changelogs/unreleased/dm-empty-state-new-merge-request.yml
@@ -0,0 +1,5 @@
+---
+title: Fix 'New merge request' button for users who don't have push access to canonical
+  project
+merge_request:
+author:
diff --git a/changelogs/unreleased/enable-webpack-code-splitting.yml b/changelogs/unreleased/enable-webpack-code-splitting.yml
new file mode 100644
index 0000000000000000000000000000000000000000..d61c3b97d11077dd49a6bc01831716e31d3d953e
--- /dev/null
+++ b/changelogs/unreleased/enable-webpack-code-splitting.yml
@@ -0,0 +1,5 @@
+---
+title: Enable support for webpack code-splitting by dynamically setting publicPath
+  at runtime
+merge_request: 12032
+author:
diff --git a/changelogs/unreleased/fix-2801.yml b/changelogs/unreleased/fix-2801.yml
new file mode 100644
index 0000000000000000000000000000000000000000..4f0aa06b6ba5a9483f1a074f3b20dbc0b8a30962
--- /dev/null
+++ b/changelogs/unreleased/fix-2801.yml
@@ -0,0 +1,4 @@
+---
+title: Expires full_path cache after a repository is renamed/transferred
+merge_request:
+author:
diff --git a/changelogs/unreleased/fix-34417.yml b/changelogs/unreleased/fix-34417.yml
deleted file mode 100644
index 5f012ad0c81ab209d3e7d40d1ea620fae41cd3b9..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/fix-34417.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Perform housekeeping only when an import of a fresh project is completed
-merge_request:
-author:
diff --git a/changelogs/unreleased/fix-assigned-issuable-lists.yml b/changelogs/unreleased/fix-assigned-issuable-lists.yml
new file mode 100644
index 0000000000000000000000000000000000000000..fc2cd18ddb6b870b28138cd3327d26a6d0185dcb
--- /dev/null
+++ b/changelogs/unreleased/fix-assigned-issuable-lists.yml
@@ -0,0 +1,5 @@
+---
+title: Add issuable-list class to shared mr/issue lists to fix new responsive layout
+  design
+merge_request:
+author:
diff --git a/changelogs/unreleased/fix-head-pipeline-for-commit-status.yml b/changelogs/unreleased/fix-head-pipeline-for-commit-status.yml
deleted file mode 100644
index f12e7b53790bbf24cad30d9af46dc5b521292ad5..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/fix-head-pipeline-for-commit-status.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Fix head pipeline stored in merge request for external pipelines
-merge_request: 12478
-author:
diff --git a/changelogs/unreleased/fix-sidebar-showing-mobile-merge-requests.yml b/changelogs/unreleased/fix-sidebar-showing-mobile-merge-requests.yml
new file mode 100644
index 0000000000000000000000000000000000000000..856990a6126642b54f81b517e13b1d84761868ab
--- /dev/null
+++ b/changelogs/unreleased/fix-sidebar-showing-mobile-merge-requests.yml
@@ -0,0 +1,4 @@
+---
+title: Fixed sidebar not collapsing on merge requests in mobile screens
+merge_request:
+author:
diff --git a/changelogs/unreleased/highest-return-on-diff-investment.yml b/changelogs/unreleased/highest-return-on-diff-investment.yml
deleted file mode 100644
index c8be1e0ff8fc03acc8f5afaaddda880ecb3e0bca..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/highest-return-on-diff-investment.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Bring back branches badge to main project page
-merge_request: 12548
-author:
diff --git a/changelogs/unreleased/issue-boards-closed-list-all.yml b/changelogs/unreleased/issue-boards-closed-list-all.yml
deleted file mode 100644
index 7643864150d1018e4f7ffb4f05845d0540e82254..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/issue-boards-closed-list-all.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Fixed issue boards closed list not showing all closed issues
-merge_request:
-author:
diff --git a/changelogs/unreleased/issue-form-multiple-line-markdown.yml b/changelogs/unreleased/issue-form-multiple-line-markdown.yml
deleted file mode 100644
index 23128f346bc2bb92ba7d24b5312ceb3f96b4f9cb..0000000000000000000000000000000000000000
--- a/changelogs/unreleased/issue-form-multiple-line-markdown.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Fixed multi-line markdown tooltip buttons in issue edit form
-merge_request:
-author:
diff --git a/changelogs/unreleased/issueable-list-cleanup.yml b/changelogs/unreleased/issueable-list-cleanup.yml
new file mode 100644
index 0000000000000000000000000000000000000000..d3d67d04574b6e33d3d537364e8ae3ebb9b6acce
--- /dev/null
+++ b/changelogs/unreleased/issueable-list-cleanup.yml
@@ -0,0 +1,4 @@
+---
+title: Clean up UI of issuable lists and make more responsive
+merge_request:
+author:
diff --git a/changelogs/unreleased/sh-fix-project-destroy-in-namespace.yml b/changelogs/unreleased/sh-fix-project-destroy-in-namespace.yml
new file mode 100644
index 0000000000000000000000000000000000000000..9309f961345f419e1c8fa61213eaee978ff37c72
--- /dev/null
+++ b/changelogs/unreleased/sh-fix-project-destroy-in-namespace.yml
@@ -0,0 +1,4 @@
+---
+title: Defer project destroys within a namespace in Groups::DestroyService#async_execute
+merge_request:
+author:
diff --git a/changelogs/unreleased/speed-up-issue-counting-for-a-project.yml b/changelogs/unreleased/speed-up-issue-counting-for-a-project.yml
new file mode 100644
index 0000000000000000000000000000000000000000..6bf03d9a3820d86631050244d7c6c26f9633dcdf
--- /dev/null
+++ b/changelogs/unreleased/speed-up-issue-counting-for-a-project.yml
@@ -0,0 +1,5 @@
+---
+title: Cache open issue and merge request counts for project tabs to speed up project
+  pages
+merge_request: 12457
+author:
diff --git a/config/webpack.config.js b/config/webpack.config.js
index 90ef6a5448b6a7eb22f08f411e489b7ffaad376a..cbb0a899638aba0a6ce3fff818ce767b5807f15e 100644
--- a/config/webpack.config.js
+++ b/config/webpack.config.js
@@ -71,6 +71,7 @@ var config = {
     vue_merge_request_widget: './vue_merge_request_widget/index.js',
     test:                 './test.js',
     peek:                 './peek.js',
+    webpack_runtime:      './webpack.js',
   },
 
   output: {
@@ -190,7 +191,7 @@ var config = {
 
     // create cacheable common library bundles
     new webpack.optimize.CommonsChunkPlugin({
-      names: ['main', 'locale', 'common', 'runtime'],
+      names: ['main', 'locale', 'common', 'webpack_runtime'],
     }),
   ],
 
@@ -245,7 +246,6 @@ if (IS_DEV_SERVER) {
     hot: DEV_SERVER_LIVERELOAD,
     inline: DEV_SERVER_LIVERELOAD
   };
-  config.output.publicPath = '//' + DEV_SERVER_HOST + ':' + DEV_SERVER_PORT + config.output.publicPath;
   config.plugins.push(
     // watch node_modules for changes if we encounter a missing module compile error
     new WatchMissingNodeModulesPlugin(path.join(ROOT_PATH, 'node_modules'))
diff --git a/doc/api/features.md b/doc/api/features.md
index 89b8d3ac9484720fc2d054305098bf55899f07ad..558869255cccdb98dc21c78f4385fcca2dbf75bd 100644
--- a/doc/api/features.md
+++ b/doc/api/features.md
@@ -58,6 +58,10 @@ POST /features/:name
 | --------- | ---- | -------- | ----------- |
 | `name` | string | yes | Name of the feature to create or update |
 | `value` | integer/string | yes | `true` or `false` to enable/disable, or an integer for percentage of time |
+| `feature_group` | string | no | A Feature group name |
+| `user` | string | no | A GitLab username |
+
+Note that `feature_group` and `user` are mutually exclusive.
 
 ```bash
 curl --data "value=30" --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/features/new_library
diff --git a/doc/api/repository_files.md b/doc/api/repository_files.md
index 18ceb8f779e5e5466b62ec551056a2b638f7e864..1fc577561a0aad81a3d158442481946b551cb9c0 100644
--- a/doc/api/repository_files.md
+++ b/doc/api/repository_files.md
@@ -61,7 +61,7 @@ POST /projects/:id/repository/files/:file_path
 ```
 
 ```bash
-curl --request POST --header 'PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK' 'https://gitlab.example.com/api/v4/projects/13083/repository/app%2Fprojectrb%2E?branch=master&author_email=author%40example.com&author_name=Firstname%20Lastname&content=some%20content&commit_message=create%20a%20new%20file'
+curl --request POST --header 'PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK' 'https://gitlab.example.com/api/v4/projects/13083/repository/files/app%2Fprojectrb%2E?branch=master&author_email=author%40example.com&author_name=Firstname%20Lastname&content=some%20content&commit_message=create%20a%20new%20file'
 ```
 
 Example response:
@@ -90,7 +90,7 @@ PUT /projects/:id/repository/files/:file_path
 ```
 
 ```bash
-curl --request PUT --header 'PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK' 'https://gitlab.example.com/api/v4/projects/13083/repository/app%2Fproject%2Erb?branch=master&author_email=author%40example.com&author_name=Firstname%20Lastname&content=some%20other%20content&commit_message=update%20file'
+curl --request PUT --header 'PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK' 'https://gitlab.example.com/api/v4/projects/13083/repository/files/app%2Fproject%2Erb?branch=master&author_email=author%40example.com&author_name=Firstname%20Lastname&content=some%20other%20content&commit_message=update%20file'
 ```
 
 Example response:
@@ -129,7 +129,7 @@ DELETE /projects/:id/repository/files/:file_path
 ```
 
 ```bash
-curl --request DELETE --header 'PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK' 'https://gitlab.example.com/api/v4/projects/13083/repository/app%2Fproject%2Erb?branch=master&author_email=author%40example.com&author_name=Firstname%20Lastname&commit_message=delete%20file'
+curl --request DELETE --header 'PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK' 'https://gitlab.example.com/api/v4/projects/13083/repository/files/app%2Fproject%2Erb?branch=master&author_email=author%40example.com&author_name=Firstname%20Lastname&commit_message=delete%20file'
 ```
 
 Example response:
diff --git a/doc/development/README.md b/doc/development/README.md
index 9496a87d84d3e610da46da10560cbd51ede152c8..a2a07c37ced5047a75a0774e04c11bf262554a41 100644
--- a/doc/development/README.md
+++ b/doc/development/README.md
@@ -54,6 +54,7 @@
 - [Polymorphic Associations](polymorphic_associations.md)
 - [Single Table Inheritance](single_table_inheritance.md)
 - [Background Migrations](background_migrations.md)
+- [Storing SHA1 Hashes As Binary](sha1_as_binary.md)
 
 ## i18n
 
diff --git a/doc/development/sha1_as_binary.md b/doc/development/sha1_as_binary.md
new file mode 100644
index 0000000000000000000000000000000000000000..3151cc29bbc67decfb378268ce5f99a36c089151
--- /dev/null
+++ b/doc/development/sha1_as_binary.md
@@ -0,0 +1,36 @@
+# Storing SHA1 Hashes As Binary
+
+Storing SHA1 hashes as strings is not very space efficient. A SHA1 as a string
+requires at least 40 bytes, an additional byte to store the encoding, and
+perhaps more space depending on the internals of PostgreSQL and MySQL.
+
+On the other hand, if one were to store a SHA1 as binary one would only need 20
+bytes for the actual SHA1, and 1 or 4 bytes of additional space (again depending
+on database internals). This means that in the best case scenario we can reduce
+the space usage by 50%.
+
+To make this easier to work with you can include the concern `ShaAttribute` into
+a model and define a SHA attribute using the `sha_attribute` class method. For
+example:
+
+```ruby
+class Commit < ActiveRecord::Base
+  include ShaAttribute
+
+  sha_attribute :sha
+end
+```
+
+This allows you to use the value of the `sha` attribute as if it were a string,
+while storing it as binary. This means that you can do something like this,
+without having to worry about converting data to the right binary format:
+
+```ruby
+commit = Commit.find_by(sha: '88c60307bd1f215095834f09a1a5cb18701ac8ad')
+commit.sha = '971604de4cfa324d91c41650fabc129420c8d1cc'
+commit.save
+```
+
+There is however one requirement: the column used to store the SHA has _must_ be
+a binary type. For Rails this means you need to use the `:binary` type instead
+of `:text` or `:string`.
diff --git a/doc/integration/external-issue-tracker.md b/doc/integration/external-issue-tracker.md
index 265c891cf8357355d10a43f62469304ef685d285..2dd9b33273ce71cb14a4d232ec3a8f59a364b1c7 100644
--- a/doc/integration/external-issue-tracker.md
+++ b/doc/integration/external-issue-tracker.md
@@ -8,6 +8,9 @@ you to do the following:
   issue index of the external tracker
 - clicking **New issue** on the project dashboard creates a new issue on the
   external tracker
+- you can reference these external issues inside GitLab interface
+  (merge requests, commits, comments) and they will be automatically converted
+  into links
 
 ## Configuration
 
diff --git a/doc/update/9.1-to-9.2.md b/doc/update/9.1-to-9.2.md
index e7d97fde14ebe0c627689f846f1c0d82a89655fa..225a4dcc924d5280d5d4a7c6bcafaf02ae17b072 100644
--- a/doc/update/9.1-to-9.2.md
+++ b/doc/update/9.1-to-9.2.md
@@ -70,7 +70,27 @@ curl --location https://yarnpkg.com/install.sh | bash -
 
 More information can be found on the [yarn website](https://yarnpkg.com/en/docs/install).
 
-### 5. Get latest code
+### 5. Update Go
+
+NOTE: GitLab 9.2 and higher only supports Go 1.8.3 and dropped support for Go
+1.5.x through 1.7.x. Be sure to upgrade your installation if necessary.
+
+You can check which version you are running with `go version`.
+
+Download and install Go:
+
+```bash
+# Remove former Go installation folder
+sudo rm -rf /usr/local/go
+
+curl --remote-name --progress https://storage.googleapis.com/golang/go1.8.3.linux-amd64.tar.gz
+echo '1862f4c3d3907e59b04a757cfda0ea7aa9ef39274af99a784f5be843c80c6772  go1.8.3.linux-amd64.tar.gz' | shasum -a256 -c - && \
+  sudo tar -C /usr/local -xzf go1.8.3.linux-amd64.tar.gz
+sudo ln -sf /usr/local/go/bin/{go,godoc,gofmt} /usr/local/bin/
+rm go1.8.3.linux-amd64.tar.gz
+```
+
+### 6. Get latest code
 
 ```bash
 cd /home/git/gitlab
@@ -97,7 +117,7 @@ cd /home/git/gitlab
 sudo -u git -H git checkout 9-2-stable-ee
 ```
 
-### 6. Update gitlab-shell
+### 7. Update gitlab-shell
 
 ```bash
 cd /home/git/gitlab-shell
@@ -107,11 +127,10 @@ sudo -u git -H git checkout v$(</home/git/gitlab/GITLAB_SHELL_VERSION)
 sudo -u git -H bin/compile
 ```
 
-### 7. Update gitlab-workhorse
+### 8. Update gitlab-workhorse
 
-Install and compile gitlab-workhorse. This requires
-[Go 1.8](https://golang.org/dl). Go (at least 1.5) should already be on your system from
-GitLab 8.1 and shall be upgraded if necessary. Please note that starting in Gitlab 9.3, only Go 1.8.3 and above will be supported. GitLab-Workhorse uses [GNU Make](https://www.gnu.org/software/make/).
+Install and compile gitlab-workhorse. GitLab-Workhorse uses
+[GNU Make](https://www.gnu.org/software/make/).
 If you are not using Linux you may have to run `gmake` instead of
 `make` below.
 
@@ -123,7 +142,7 @@ sudo -u git -H git checkout v$(</home/git/gitlab/GITLAB_WORKHORSE_VERSION)
 sudo -u git -H make
 ```
 
-### 8. Update configuration files
+### 9. Update configuration files
 
 #### New configuration options for `gitlab.yml`
 
@@ -197,7 +216,7 @@ For Ubuntu 16.04.1 LTS:
 sudo systemctl daemon-reload
 ```
 
-### 9. Install libs, migrations, etc.
+### 10. Install libs, migrations, etc.
 
 ```bash
 cd /home/git/gitlab
@@ -223,7 +242,7 @@ sudo -u git -H bundle exec rake cache:clear RAILS_ENV=production
 
 **MySQL installations**: Run through the `MySQL strings limits` and `Tables and data conversion to utf8mb4` [tasks](../install/database_mysql.md).
 
-### 10. Optional: install Gitaly
+### 11. Optional: install Gitaly
 
 Gitaly is still an optional component of GitLab. If you want to save time
 during your 9.2 upgrade **you can skip this step**.
@@ -240,14 +259,14 @@ sudo -u git -H git checkout v$(</home/git/gitlab/GITALY_SERVER_VERSION)
 sudo -u git -H make
 ```
 
-### 11. Start application
+### 12. Start application
 
 ```bash
 sudo service gitlab start
 sudo service nginx restart
 ```
 
-### 12. Check application status
+### 13. Check application status
 
 Check if GitLab and its environment are configured correctly:
 
diff --git a/doc/update/9.2-to-9.3.md b/doc/update/9.2-to-9.3.md
index 8fbcc892fd5154db118e798218f9a905e40be6cf..a2dc1dadcdcaefb9ff473a4dcc13de4ce6e14582 100644
--- a/doc/update/9.2-to-9.3.md
+++ b/doc/update/9.2-to-9.3.md
@@ -72,8 +72,8 @@ More information can be found on the [yarn website](https://yarnpkg.com/en/docs/
 
 ### 5. Update Go
 
-NOTE: GitLab 9.3 and higher only supports Go 1.8.3 and dropped support for Go 1.5.x through 1.7.x. Be
-sure to upgrade your installation if necessary
+NOTE: GitLab 9.2 and higher only supports Go 1.8.3 and dropped support for Go
+1.5.x through 1.7.x. Be sure to upgrade your installation if necessary.
 
 You can check which version you are running with `go version`.
 
@@ -129,9 +129,8 @@ sudo -u git -H bin/compile
 
 ### 8. Update gitlab-workhorse
 
-Install and compile gitlab-workhorse. This requires
-[Go 1.5](https://golang.org/dl) which should already be on your system from
-GitLab 8.1. GitLab-Workhorse uses [GNU Make](https://www.gnu.org/software/make/).
+Install and compile gitlab-workhorse. GitLab-Workhorse uses
+[GNU Make](https://www.gnu.org/software/make/).
 If you are not using Linux you may have to run `gmake` instead of
 `make` below.
 
diff --git a/doc/user/project/img/issue_board.png b/doc/user/project/img/issue_board.png
index b636cb294b8f15eb6bea243ab6d8ab75926a4536..cf7f519f783bff7fdd7ce00def10bd9b1417cb39 100644
Binary files a/doc/user/project/img/issue_board.png and b/doc/user/project/img/issue_board.png differ
diff --git a/doc/user/project/img/issue_board_add_list.png b/doc/user/project/img/issue_board_add_list.png
index cdfc466d23f2072888da452496026af56cf764f7..973d9f7cde4fe9e63147d20238294b053ee18b99 100644
Binary files a/doc/user/project/img/issue_board_add_list.png and b/doc/user/project/img/issue_board_add_list.png differ
diff --git a/doc/user/project/img/issue_board_welcome_message.png b/doc/user/project/img/issue_board_welcome_message.png
index 5318e6ea4a9071175ed8c695be157856bc899ec2..127b9b08cc7d0ee2fddef63c13baa85aab38628d 100644
Binary files a/doc/user/project/img/issue_board_welcome_message.png and b/doc/user/project/img/issue_board_welcome_message.png differ
diff --git a/doc/user/project/img/issue_boards_add_issues_modal.png b/doc/user/project/img/issue_boards_add_issues_modal.png
index 33049dce74ffd65399b1d4c8fed4c38eb902fb81..bedaf724a15f729485b6cc84e0c390613c021468 100644
Binary files a/doc/user/project/img/issue_boards_add_issues_modal.png and b/doc/user/project/img/issue_boards_add_issues_modal.png differ
diff --git a/doc/user/project/integrations/bugzilla.md b/doc/user/project/integrations/bugzilla.md
index 0b219e8447834a1d704ee0a6012710d0c1063261..6a040516231a0de0500258ce13af2ba2f98e2e62 100644
--- a/doc/user/project/integrations/bugzilla.md
+++ b/doc/user/project/integrations/bugzilla.md
@@ -16,3 +16,14 @@ Once you have configured and enabled Bugzilla:
 - the **Issues** link on the GitLab project pages takes you to the appropriate
   Bugzilla product page
 - clicking **New issue** on the project dashboard takes you to Bugzilla for entering a new issue
+
+## Referencing issues in Bugzilla
+
+Issues in Bugzilla can be referenced in two alternative ways:
+1. `#<ID>` where `<ID>` is a number (example `#143`)
+2. `<PROJECT>-<ID>` where `<PROJECT>` starts with a capital letter which is
+  then followed by capital letters, numbers or underscores, and `<ID>` is
+  a number (example `API_32-143`).
+
+Please note that `<PROJECT>` part is ignored and links always point to the
+address specified in `issues_url`.
diff --git a/doc/user/project/integrations/redmine.md b/doc/user/project/integrations/redmine.md
index 89c0312d3c26260f642e3529217df702826edcab..8026f1f57bcf8da3f194fe9dd1e34193bc0e0ec1 100644
--- a/doc/user/project/integrations/redmine.md
+++ b/doc/user/project/integrations/redmine.md
@@ -21,3 +21,14 @@ Once you have configured and enabled Redmine:
 As an example, below is a configuration for a project named gitlab-ci.
 
 ![Redmine configuration](img/redmine_configuration.png)
+
+## Referencing issues in Redmine
+
+Issues in Redmine can be referenced in two alternative ways:
+1. `#<ID>` where `<ID>` is a number (example `#143`)
+2. `<PROJECT>-<ID>` where `<PROJECT>` starts with a capital letter which is
+  then followed by capital letters, numbers or underscores, and `<ID>` is
+  a number (example `API_32-143`).
+
+Please note that `<PROJECT>` part is ignored and links always point to the
+address specified in `issues_url`.
diff --git a/doc/workflow/gitlab_flow.md b/doc/workflow/gitlab_flow.md
index e10ccc4fc46d84deb31378d8e480353c54519e39..ea28968fbb2fa4dfac6404c69109483db818f8f8 100644
--- a/doc/workflow/gitlab_flow.md
+++ b/doc/workflow/gitlab_flow.md
@@ -300,7 +300,7 @@ If there are no merge conflicts and the feature branches are short lived the ris
 If there are merge conflicts you merge the master branch into the feature branch and the CI server will rerun the tests.
 If you have long lived feature branches that last for more than a few days you should make your issues smaller.
 
-## Working wih feature branches
+## Working with feature branches
 
 ![Shell output showing git pull output](git_pull.png)
 
diff --git a/features/dashboard/new_project.feature b/features/dashboard/new_project.feature
deleted file mode 100644
index 046e2815d4ea8d3735c1f5c94c712d21ea701454..0000000000000000000000000000000000000000
--- a/features/dashboard/new_project.feature
+++ /dev/null
@@ -1,30 +0,0 @@
-@dashboard
-Feature: New Project
-Background:
-  Given I sign in as a user
-  And I own project "Shop"
-  And I visit dashboard page
-  And I click "New project" link
-
-  @javascript
-  Scenario: I should see New Projects page
-  Then I see "New Project" page
-  Then I see all possible import options
-
-  @javascript
-  Scenario: I should see instructions on how to import from Git URL
-  Given I see "New Project" page
-  When I click on "Repo by URL"
-  Then I see instructions on how to import from Git URL
-
-  @javascript
-  Scenario: I should see instructions on how to import from GitHub
-  Given I see "New Project" page
-  When I click on "Import project from GitHub"
-  Then I am redirected to the GitHub import page
-
-  @javascript
-  Scenario: I should see Google Code import page
-  Given I see "New Project" page
-  When I click on "Google Code"
-  Then I redirected to Google Code import page
diff --git a/features/steps/dashboard/new_project.rb b/features/steps/dashboard/new_project.rb
deleted file mode 100644
index 530fd6f7bdb8148995dc20dcb3824b54dcd51c60..0000000000000000000000000000000000000000
--- a/features/steps/dashboard/new_project.rb
+++ /dev/null
@@ -1,59 +0,0 @@
-class Spinach::Features::NewProject < Spinach::FeatureSteps
-  include SharedAuthentication
-  include SharedPaths
-  include SharedProject
-
-  step 'I click "New project" link' do
-    page.within '#content-body' do
-      click_link "New project"
-    end
-  end
-
-  step 'I click "New project" in top right menu' do
-    page.within '.header-content' do
-      click_link "New project"
-    end
-  end
-
-  step 'I see "New Project" page' do
-    expect(page).to have_content('Project path')
-    expect(page).to have_content('Project name')
-  end
-
-  step 'I see all possible import options' do
-    expect(page).to have_link('GitHub')
-    expect(page).to have_link('Bitbucket')
-    expect(page).to have_link('GitLab.com')
-    expect(page).to have_link('Google Code')
-    expect(page).to have_button('Repo by URL')
-    expect(page).to have_link('GitLab export')
-  end
-
-  step 'I click on "Import project from GitHub"' do
-    first('.import_github').click
-  end
-
-  step 'I am redirected to the GitHub import page' do
-    expect(page).to have_content('Import Projects from GitHub')
-    expect(current_path).to eq new_import_github_path
-  end
-
-  step 'I click on "Repo by URL"' do
-    first('.import_git').click
-  end
-
-  step 'I see instructions on how to import from Git URL' do
-    git_import_instructions = first('.js-toggle-content')
-    expect(git_import_instructions).to be_visible
-    expect(git_import_instructions).to have_content "Git repository URL"
-  end
-
-  step 'I click on "Google Code"' do
-    first('.import_google_code').click
-  end
-
-  step 'I redirected to Google Code import page' do
-    expect(page).to have_content('Import projects from Google Code')
-    expect(current_path).to eq new_import_google_code_path
-  end
-end
diff --git a/features/steps/dashboard/starred_projects.rb b/features/steps/dashboard/starred_projects.rb
deleted file mode 100644
index c33813e550b8e7c3f5b51cf202f46e1a5694ce17..0000000000000000000000000000000000000000
--- a/features/steps/dashboard/starred_projects.rb
+++ /dev/null
@@ -1,15 +0,0 @@
-class Spinach::Features::DashboardStarredProjects < Spinach::FeatureSteps
-  include SharedAuthentication
-  include SharedPaths
-  include SharedProject
-
-  step 'I starred project "Community"' do
-    current_user.toggle_star(Project.find_by(name: 'Community'))
-  end
-
-  step 'I should not see project "Shop"' do
-    page.within '.projects-list' do
-      expect(page).not_to have_content('Shop')
-    end
-  end
-end
diff --git a/features/steps/project/merge_requests.rb b/features/steps/project/merge_requests.rb
index 69f5d0f8410319d516ad1aa1f0f62b0021cb4428..dceeed5aafebea0dbcc970f358e6b522ae57efc2 100644
--- a/features/steps/project/merge_requests.rb
+++ b/features/steps/project/merge_requests.rb
@@ -65,7 +65,7 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps
   end
 
   step 'I should not see "master" branch' do
-    expect(find('.merge-request-info')).not_to have_content "master"
+    expect(find('.issuable-info')).not_to have_content "master"
   end
 
   step 'I should see "feature_conflict" branch' do
diff --git a/features/steps/shared/diff_note.rb b/features/steps/shared/diff_note.rb
index 36fc315599e3b99bfd19b741898304bc54bad988..2c59ec5bb060a0d1e40ebfc55f55953029d096e9 100644
--- a/features/steps/shared/diff_note.rb
+++ b/features/steps/shared/diff_note.rb
@@ -232,7 +232,7 @@ module SharedDiffNote
   end
 
   def click_parallel_diff_line(code, line_type)
-    find(".line_content.parallel.#{line_type}[data-line-code='#{code}']").trigger 'mouseover'
+    find(".line_holder.parallel .diff-line-num[id='#{code}']").trigger 'mouseover'
     find(".line_holder.parallel button[data-line-code='#{code}']").trigger 'click'
   end
 end
diff --git a/lib/api/features.rb b/lib/api/features.rb
index cff0ba2ddff3f29fd0df2a0a0fe0c5ab24c8a9d2..217459164638068adbf9d6bf166c1793edcffd0e 100644
--- a/lib/api/features.rb
+++ b/lib/api/features.rb
@@ -2,6 +2,29 @@ module API
   class Features < Grape::API
     before { authenticated_as_admin! }
 
+    helpers do
+      def gate_value(params)
+        case params[:value]
+        when 'true'
+          true
+        when '0', 'false'
+          false
+        else
+          params[:value].to_i
+        end
+      end
+
+      def gate_target(params)
+        if params[:feature_group]
+          Feature.group(params[:feature_group])
+        elsif params[:user]
+          User.find_by_username(params[:user])
+        else
+          gate_value(params)
+        end
+      end
+    end
+
     resource :features do
       desc 'Get a list of all features' do
         success Entities::Feature
@@ -17,16 +40,22 @@ module API
       end
       params do
         requires :value, type: String, desc: '`true` or `false` to enable/disable, an integer for percentage of time'
+        optional :feature_group, type: String, desc: 'A Feature group name'
+        optional :user, type: String, desc: 'A GitLab username'
+        mutually_exclusive :feature_group, :user
       end
       post ':name' do
         feature = Feature.get(params[:name])
+        target = gate_target(params)
+        value = gate_value(params)
 
-        if %w(0 false).include?(params[:value])
-          feature.disable
-        elsif params[:value] == 'true'
-          feature.enable
+        case value
+        when true
+          feature.enable(target)
+        when false
+          feature.disable(target)
         else
-          feature.enable_percentage_of_time(params[:value].to_i)
+          feature.enable_percentage_of_time(value)
         end
 
         present feature, with: Entities::Feature, current_user: current_user
diff --git a/lib/api/projects.rb b/lib/api/projects.rb
index 886e97a263852aac1424dcd9d2039d40aefd0488..d0bd64b2972f9751055c5370629041521615db37 100644
--- a/lib/api/projects.rb
+++ b/lib/api/projects.rb
@@ -1,4 +1,4 @@
-require 'declarative_policy'
+require_dependency 'declarative_policy'
 
 module API
   # Projects API
diff --git a/lib/banzai/filter/abstract_reference_filter.rb b/lib/banzai/filter/abstract_reference_filter.rb
index 8bc2dd18bdadd2ae37c93fbbdedf1cedca6f3089..7a262dd025ce0555351fe5cc44166536f1c8d418 100644
--- a/lib/banzai/filter/abstract_reference_filter.rb
+++ b/lib/banzai/filter/abstract_reference_filter.rb
@@ -216,12 +216,7 @@ module Banzai
         @references_per_project ||= begin
           refs = Hash.new { |hash, key| hash[key] = Set.new }
 
-          regex =
-            if uses_reference_pattern?
-              Regexp.union(object_class.reference_pattern, object_class.link_reference_pattern)
-            else
-              object_class.link_reference_pattern
-            end
+          regex = Regexp.union(object_class.reference_pattern, object_class.link_reference_pattern)
 
           nodes.each do |node|
             node.to_html.scan(regex) do
@@ -323,14 +318,6 @@ module Banzai
           value
         end
       end
-
-      # There might be special cases like filters
-      # that should ignore reference pattern
-      # eg: IssueReferenceFilter when using a external issues tracker
-      # In those cases this method should be overridden on the filter subclass
-      def uses_reference_pattern?
-        true
-      end
     end
   end
 end
diff --git a/lib/banzai/filter/external_issue_reference_filter.rb b/lib/banzai/filter/external_issue_reference_filter.rb
index dce4de3ceaf800acb5dc6d1237f8646796046d70..53a229256a5f0ac5aeb5f1b5c39cc637ef3b974a 100644
--- a/lib/banzai/filter/external_issue_reference_filter.rb
+++ b/lib/banzai/filter/external_issue_reference_filter.rb
@@ -3,6 +3,8 @@ module Banzai
     # HTML filter that replaces external issue tracker references with links.
     # References are ignored if the project doesn't use an external issue
     # tracker.
+    #
+    # This filter does not support cross-project references.
     class ExternalIssueReferenceFilter < ReferenceFilter
       self.reference_type = :external_issue
 
@@ -87,7 +89,7 @@ module Banzai
       end
 
       def issue_reference_pattern
-        external_issues_cached(:issue_reference_pattern)
+        external_issues_cached(:external_issue_reference_pattern)
       end
 
       private
diff --git a/lib/banzai/filter/issue_reference_filter.rb b/lib/banzai/filter/issue_reference_filter.rb
index 044d18ff8241ef951a72af7f4253733f68c8f447..ba1a5ac84b31c98c2da1529b9df10b8e312857d9 100644
--- a/lib/banzai/filter/issue_reference_filter.rb
+++ b/lib/banzai/filter/issue_reference_filter.rb
@@ -15,10 +15,6 @@ module Banzai
         Issue
       end
 
-      def uses_reference_pattern?
-        context[:project].default_issues_tracker?
-      end
-
       def find_object(project, iid)
         issues_per_project[project][iid]
       end
@@ -38,13 +34,7 @@ module Banzai
 
           projects_per_reference.each do |path, project|
             issue_ids = references_per_project[path]
-
-            issues =
-              if project.default_issues_tracker?
-                project.issues.where(iid: issue_ids.to_a)
-              else
-                issue_ids.map { |id| ExternalIssue.new(id, project) }
-              end
+            issues = project.issues.where(iid: issue_ids.to_a)
 
             issues.each do |issue|
               hash[project][issue.iid.to_i] = issue
@@ -55,26 +45,6 @@ module Banzai
         end
       end
 
-      def object_link_title(object)
-        if object.is_a?(ExternalIssue)
-          "Issue in #{object.project.external_issue_tracker.title}"
-        else
-          super
-        end
-      end
-
-      def data_attributes_for(text, project, object, link: false)
-        if object.is_a?(ExternalIssue)
-          data_attribute(
-            project: project.id,
-            external_issue: object.id,
-            reference_type: ExternalIssueReferenceFilter.reference_type
-          )
-        else
-          super
-        end
-      end
-
       def projects_relation_for_paths(paths)
         super(paths).includes(:gitlab_issue_tracker_service)
       end
diff --git a/lib/banzai/reference_parser/issue_parser.rb b/lib/banzai/reference_parser/issue_parser.rb
index 9fd4bd68d4396faaf7910c9a481cdc0cfcb61305..a65bbe23958c3808bd19923eace715ba72934f6d 100644
--- a/lib/banzai/reference_parser/issue_parser.rb
+++ b/lib/banzai/reference_parser/issue_parser.rb
@@ -4,9 +4,6 @@ module Banzai
       self.reference_type = :issue
 
       def nodes_visible_to_user(user, nodes)
-        # It is not possible to check access rights for external issue trackers
-        return nodes if project && project.external_issue_tracker
-
         issues = issues_for_nodes(nodes)
 
         readable_issues = Ability
diff --git a/lib/feature.rb b/lib/feature.rb
index d3d972564af7803888ea10523f6a66be27401e82..363f66ba60eec2be663164881d412adb7abdf62c 100644
--- a/lib/feature.rb
+++ b/lib/feature.rb
@@ -12,6 +12,8 @@ class Feature
   end
 
   class << self
+    delegate :group, to: :flipper
+
     def all
       flipper.features.to_a
     end
@@ -27,16 +29,24 @@ class Feature
       all.map(&:name).include?(feature.name)
     end
 
-    def enabled?(key)
-      get(key).enabled?
+    def enabled?(key, thing = nil)
+      get(key).enabled?(thing)
+    end
+
+    def enable(key, thing = true)
+      get(key).enable(thing)
+    end
+
+    def disable(key, thing = false)
+      get(key).disable(thing)
     end
 
-    def enable(key)
-      get(key).enable
+    def enable_group(key, group)
+      get(key).enable_group(group)
     end
 
-    def disable(key)
-      get(key).disable
+    def disable_group(key, group)
+      get(key).disable_group(group)
     end
 
     def flipper
diff --git a/lib/gitlab/ci/config/entry/image.rb b/lib/gitlab/ci/config/entry/image.rb
index 897dcff80122eb4cbdfe52a08569c18287ca5d5b..6555c5891736aa9eba06abeacf27e33682c425da 100644
--- a/lib/gitlab/ci/config/entry/image.rb
+++ b/lib/gitlab/ci/config/entry/image.rb
@@ -15,7 +15,7 @@ module Gitlab
             validates :config, allowed_keys: ALLOWED_KEYS
 
             validates :name, type: String, presence: true
-            validates :entrypoint, type: String, allow_nil: true
+            validates :entrypoint, array_of_strings: true, allow_nil: true
           end
 
           def hash?
diff --git a/lib/gitlab/ci/config/entry/service.rb b/lib/gitlab/ci/config/entry/service.rb
index b52faf48b58a63a139f895bab9f711ade8d707fe..3e2ebcff31a09df095433da33becc11221d7098b 100644
--- a/lib/gitlab/ci/config/entry/service.rb
+++ b/lib/gitlab/ci/config/entry/service.rb
@@ -15,8 +15,8 @@ module Gitlab
             validates :config, allowed_keys: ALLOWED_KEYS
 
             validates :name, type: String, presence: true
-            validates :entrypoint, type: String, allow_nil: true
-            validates :command, type: String, allow_nil: true
+            validates :entrypoint, array_of_strings: true, allow_nil: true
+            validates :command, array_of_strings: true, allow_nil: true
             validates :alias, type: String, allow_nil: true
           end
 
diff --git a/lib/gitlab/database/sha_attribute.rb b/lib/gitlab/database/sha_attribute.rb
new file mode 100644
index 0000000000000000000000000000000000000000..d9400e04b83997e0e3ecd9fa289848ba47375942
--- /dev/null
+++ b/lib/gitlab/database/sha_attribute.rb
@@ -0,0 +1,34 @@
+module Gitlab
+  module Database
+    BINARY_TYPE = if Gitlab::Database.postgresql?
+                    # PostgreSQL defines its own class with slightly different
+                    # behaviour from the default Binary type.
+                    ActiveRecord::ConnectionAdapters::PostgreSQL::OID::Bytea
+                  else
+                    ActiveRecord::Type::Binary
+                  end
+
+    # Class for casting binary data to hexadecimal SHA1 hashes (and vice-versa).
+    #
+    # Using ShaAttribute allows you to store SHA1 values as binary while still
+    # using them as if they were stored as string values. This gives you the
+    # ease of use of string values, but without the storage overhead.
+    class ShaAttribute < BINARY_TYPE
+      PACK_FORMAT = 'H*'.freeze
+
+      # Casts binary data to a SHA1 in hexadecimal.
+      def type_cast_from_database(value)
+        value = super
+
+        value ? value.unpack(PACK_FORMAT)[0] : nil
+      end
+
+      # Casts a SHA1 in hexadecimal to the proper binary format.
+      def type_cast_for_database(value)
+        arg = value ? [value].pack(PACK_FORMAT) : nil
+
+        super(arg)
+      end
+    end
+  end
+end
diff --git a/lib/gitlab/git/hook.rb b/lib/gitlab/git/hook.rb
index bd90d24a2ecd333477cf96e530215a935f562f39..5042916343be4ae2f1e53d2ffe67982479cb39e8 100644
--- a/lib/gitlab/git/hook.rb
+++ b/lib/gitlab/git/hook.rb
@@ -4,9 +4,10 @@ module Gitlab
       GL_PROTOCOL = 'web'.freeze
       attr_reader :name, :repo_path, :path
 
-      def initialize(name, repo_path)
+      def initialize(name, project)
         @name = name
-        @repo_path = repo_path
+        @project = project
+        @repo_path = project.repository.path
         @path = File.join(repo_path.strip, 'hooks', name)
       end
 
@@ -38,7 +39,8 @@ module Gitlab
         vars = {
           'GL_ID' => gl_id,
           'PWD' => repo_path,
-          'GL_PROTOCOL' => GL_PROTOCOL
+          'GL_PROTOCOL' => GL_PROTOCOL,
+          'GL_REPOSITORY' => Gitlab::GlRepository.gl_repository(@project, false)
         }
 
         options = {
diff --git a/lib/gitlab/gon_helper.rb b/lib/gitlab/gon_helper.rb
index 319633656fff63ff8596f85b28a0210d85b2dda4..2d1ae6a5925de90a6383b3317c16d35c97b1a48e 100644
--- a/lib/gitlab/gon_helper.rb
+++ b/lib/gitlab/gon_helper.rb
@@ -2,11 +2,14 @@
 
 module Gitlab
   module GonHelper
+    include WebpackHelper
+
     def add_gon_variables
       gon.api_version            = 'v4'
       gon.default_avatar_url     = URI.join(Gitlab.config.gitlab.url, ActionController::Base.helpers.image_path('no_avatar.png')).to_s
       gon.max_file_size          = current_application_settings.max_attachment_size
       gon.asset_host             = ActionController::Base.asset_host
+      gon.webpack_public_path    = webpack_public_path
       gon.relative_url_root      = Gitlab.config.gitlab.relative_url_root
       gon.shortcuts_path         = help_page_path('shortcuts')
       gon.user_color_scheme      = Gitlab::ColorSchemes.for_user(current_user).css_class
diff --git a/locale/zh_TW/gitlab.po b/locale/zh_TW/gitlab.po
index 5130572d7ed51ce9460f4403e6ad847ae74436db..bb2b84c67b0843a508da476d25f811c26658d545 100644
--- a/locale/zh_TW/gitlab.po
+++ b/locale/zh_TW/gitlab.po
@@ -1,128 +1,460 @@
-# SOME DESCRIPTIVE TITLE.
-# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
-# This file is distributed under the same license as the gitlab package.
-# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
-#
+# Huang Tao <htve@outlook.com>, 2017. #zanata
+# Lin Jen-Shin <anonymous@domain.com>, 2017.
+# Hazel Yang <anonymous@domain.com>, 2017.
+# TzeKei Lee <anonymous@domain.com>, 2017.
+# Jerry Ho <a29988122@gmail.com>, 2017.
 msgid ""
 msgstr ""
 "Project-Id-Version: gitlab 1.0.0\n"
 "Report-Msgid-Bugs-To: \n"
-"PO-Revision-Date: 2017-05-04 19:24-0500\n"
-"Last-Translator: HuangTao <htve@outlook.com>, 2017\n"
-"Language-Team: Chinese (Taiwan) (https://www.transifex.com/gitlab-zh/teams/751"
-"77/zh_TW/)\n"
+"POT-Creation-Date: 2017-06-15 21:59-0500\n"
 "MIME-Version: 1.0\n"
 "Content-Type: text/plain; charset=UTF-8\n"
 "Content-Transfer-Encoding: 8bit\n"
-"Language: zh_TW\n"
-"Plural-Forms: nplurals=1; plural=0;\n"
+"PO-Revision-Date: 2017-06-28 11:13-0400\n"
+"Last-Translator: Huang Tao <htve@outlook.com>\n"
+"Language-Team: Chinese (Taiwan) (https://translate.zanata.org/project/view/GitLab)\n"
+"Language: zh-TW\n"
+"X-Generator: Zanata 3.9.6\n"
+"Plural-Forms: nplurals=1; plural=0\n"
+
+msgid "%{commit_author_link} committed %{commit_timeago}"
+msgstr "%{commit_author_link} 在 %{commit_timeago} 送交"
+
+msgid "About auto deploy"
+msgstr "關於自動部署"
+
+msgid "Active"
+msgstr "啟用"
+
+msgid "Activity"
+msgstr "活動"
+
+msgid "Add Changelog"
+msgstr "新增更新日誌"
+
+msgid "Add Contribution guide"
+msgstr "新增協作指南"
+
+msgid "Add License"
+msgstr "新增授權條款"
+
+msgid "Add an SSH key to your profile to pull or push via SSH."
+msgstr "請先新增 SSH 金鑰到您的個人帳號,才能使用 SSH 來上傳 (push) 或下載 (pull) 。"
+
+msgid "Add new directory"
+msgstr "新增目錄"
+
+msgid "Archived project! Repository is read-only"
+msgstr "此專案已封存!檔案庫 (repository) 為唯讀狀態"
 
 msgid "Are you sure you want to delete this pipeline schedule?"
+msgstr "確定要刪除此流水線 (pipeline) 排程嗎?"
+
+msgid "Attach a file by drag &amp; drop or %{upload_link}"
+msgstr "拖放檔案到此處或者 %{upload_link}"
+
+msgid "Branch"
+msgid_plural "Branches"
+msgstr[0] "分支 (branch) "
+
+msgid ""
+"Branch <strong>%{branch_name}</strong> was created. To set up auto deploy, "
+"choose a GitLab CI Yaml template and commit your changes. "
+"%{link_to_autodeploy_doc}"
 msgstr ""
+"已建立分支 (branch)  <strong>%{branch_name}</strong> 。如要設定自動部署, 請選擇合適的 GitLab CI "
+"Yaml 模板,然後記得要送交 (commit) 您的編輯內容。%{link_to_autodeploy_doc}\n"
+
+msgid "Branches"
+msgstr "分支 (branch) "
+
+msgid "Browse files"
+msgstr "瀏覽檔案"
 
 msgid "ByAuthor|by"
-msgstr "作者:"
+msgstr "作者:"
+
+msgid "CI configuration"
+msgstr "CI 組態"
 
 msgid "Cancel"
-msgstr ""
+msgstr "取消"
+
+msgid "ChangeTypeActionLabel|Pick into branch"
+msgstr "挑選到分支 (branch) "
+
+msgid "ChangeTypeActionLabel|Revert in branch"
+msgstr "還原分支 (branch) "
+
+msgid "ChangeTypeAction|Cherry-pick"
+msgstr "挑選"
+
+msgid "ChangeTypeAction|Revert"
+msgstr "還原"
+
+msgid "Changelog"
+msgstr "更新日誌"
+
+msgid "Charts"
+msgstr "統計圖"
+
+msgid "Cherry-pick this commit"
+msgstr "挑選此更動記錄 (commit) "
+
+msgid "Cherry-pick this merge request"
+msgstr "挑選此合併請求 (merge request) "
+
+msgid "CiStatusLabel|canceled"
+msgstr "已取消"
+
+msgid "CiStatusLabel|created"
+msgstr "已建立"
+
+msgid "CiStatusLabel|failed"
+msgstr "失敗"
+
+msgid "CiStatusLabel|manual action"
+msgstr "手動操作"
+
+msgid "CiStatusLabel|passed"
+msgstr "已通過"
+
+msgid "CiStatusLabel|passed with warnings"
+msgstr "通過,但有警告訊息"
+
+msgid "CiStatusLabel|pending"
+msgstr "等待中"
+
+msgid "CiStatusLabel|skipped"
+msgstr "已跳過"
+
+msgid "CiStatusLabel|waiting for manual action"
+msgstr "等待手動操作"
+
+msgid "CiStatusText|blocked"
+msgstr "已阻擋"
+
+msgid "CiStatusText|canceled"
+msgstr "已取消"
+
+msgid "CiStatusText|created"
+msgstr "已建立"
+
+msgid "CiStatusText|failed"
+msgstr "失敗"
+
+msgid "CiStatusText|manual"
+msgstr "手動操作"
+
+msgid "CiStatusText|passed"
+msgstr "已通過"
+
+msgid "CiStatusText|pending"
+msgstr "等待中"
+
+msgid "CiStatusText|skipped"
+msgstr "已跳過"
+
+msgid "CiStatus|running"
+msgstr "執行中"
 
 msgid "Commit"
 msgid_plural "Commits"
-msgstr[0] "送交"
+msgstr[0] "更動記錄 (commit) "
+
+msgid "Commit message"
+msgstr "更動說明 (commit) "
+
+msgid "CommitBoxTitle|Commit"
+msgstr "送交"
+
+msgid "CommitMessage|Add %{file_name}"
+msgstr "建立 %{file_name}"
+
+msgid "Commits"
+msgstr "更動記錄 (commit) "
+
+msgid "Commits|History"
+msgstr "過去更動 (commit) "
+
+msgid "Committed by"
+msgstr "送交者為 "
+
+msgid "Compare"
+msgstr "比較"
+
+msgid "Contribution guide"
+msgstr "協作指南"
+
+msgid "Contributors"
+msgstr "協作者"
+
+msgid "Copy URL to clipboard"
+msgstr "複製網址到剪貼簿"
+
+msgid "Copy commit SHA to clipboard"
+msgstr "複製更動記錄 (commit) 的 SHA 值到剪貼簿"
+
+msgid "Create New Directory"
+msgstr "建立新目錄"
+
+msgid "Create directory"
+msgstr "建立目錄"
+
+msgid "Create empty bare repository"
+msgstr "建立一個新的 bare repository"
+
+msgid "Create merge request"
+msgstr "發出合併請求 (merge request) "
+
+msgid "Create new..."
+msgstr "建立..."
+
+msgid "CreateNewFork|Fork"
+msgstr "分支 (fork) "
+
+msgid "CreateTag|Tag"
+msgstr "建立標籤"
 
 msgid "Cron Timezone"
+msgstr "Cron 時區"
+
+msgid "Cron syntax"
+msgstr "Cron 語法"
+
+msgid "Custom notification events"
+msgstr "自訂事件通知"
+
+msgid ""
+"Custom notification levels are the same as participating levels. With custom "
+"notification levels you will also receive notifications for select events. "
+"To find out more, check out %{notification_link}."
 msgstr ""
+"自訂通知層級相當於參與度設定。使用自訂通知層級,您可以只收到特定的事件通知。請參照 %{notification_link} 以獲得更多訊息。"
 
-msgid "Cycle Analytics gives an overview of how much time it takes to go from idea to production in your project."
-msgstr "週期分析概述了你的專案從想法到產品實現,各階段所需的時間。"
+msgid "Cycle Analytics"
+msgstr "週期分析"
+
+msgid ""
+"Cycle Analytics gives an overview of how much time it takes to go from idea "
+"to production in your project."
+msgstr "週期分析讓您可以有效的釐清專案從發想到產品推出所花的時間長短。"
 
 msgid "CycleAnalyticsStage|Code"
 msgstr "程式開發"
 
 msgid "CycleAnalyticsStage|Issue"
-msgstr "議題"
+msgstr "議題 (issue) "
 
 msgid "CycleAnalyticsStage|Plan"
 msgstr "計劃"
 
 msgid "CycleAnalyticsStage|Production"
-msgstr "上線"
+msgstr "營運"
 
 msgid "CycleAnalyticsStage|Review"
 msgstr "複閱"
 
 msgid "CycleAnalyticsStage|Staging"
-msgstr "預備"
+msgstr "試營運"
 
 msgid "CycleAnalyticsStage|Test"
 msgstr "測試"
 
+msgid "Define a custom pattern with cron syntax"
+msgstr "使用 Cron 語法自訂排程"
+
 msgid "Delete"
-msgstr ""
+msgstr "刪除"
 
 msgid "Deploy"
 msgid_plural "Deploys"
 msgstr[0] "部署"
 
 msgid "Description"
-msgstr ""
+msgstr "描述"
+
+msgid "Directory name"
+msgstr "目錄名稱"
+
+msgid "Don't show again"
+msgstr "不再顯示"
+
+msgid "Download"
+msgstr "下載"
+
+msgid "Download tar"
+msgstr "下載 tar"
+
+msgid "Download tar.bz2"
+msgstr "下載 tar.bz2"
+
+msgid "Download tar.gz"
+msgstr "下載 tar.gz"
+
+msgid "Download zip"
+msgstr "下載 zip"
+
+msgid "DownloadArtifacts|Download"
+msgstr "下載"
+
+msgid "DownloadCommit|Email Patches"
+msgstr "電子郵件修補檔案 (patch)"
+
+msgid "DownloadCommit|Plain Diff"
+msgstr "差異檔 (diff)"
+
+msgid "DownloadSource|Download"
+msgstr "下載原始碼"
 
 msgid "Edit"
-msgstr ""
+msgstr "編輯"
 
 msgid "Edit Pipeline Schedule %{id}"
-msgstr ""
+msgstr "編輯 %{id} 流水線 (pipeline) 排程"
+
+msgid "Every day (at 4:00am)"
+msgstr "每日執行(淩晨四點)"
+
+msgid "Every month (on the 1st at 4:00am)"
+msgstr "每月執行(每月一日淩晨四點)"
+
+msgid "Every week (Sundays at 4:00am)"
+msgstr "每週執行(週日淩晨 四點)"
 
 msgid "Failed to change the owner"
-msgstr ""
+msgstr "無法變更所有權"
 
 msgid "Failed to remove the pipeline schedule"
-msgstr ""
+msgstr "無法刪除流水線 (pipeline) 排程"
 
-msgid "Filter"
-msgstr ""
+msgid "Files"
+msgstr "檔案"
+
+msgid "Find by path"
+msgstr "以路徑搜尋"
+
+msgid "Find file"
+msgstr "搜尋檔案"
 
 msgid "FirstPushedBy|First"
-msgstr "首次推送"
+msgstr "首次推送 (push) "
 
 msgid "FirstPushedBy|pushed by"
-msgstr "推送者:"
+msgstr "推送者 (push) :"
+
+msgid "Fork"
+msgid_plural "Forks"
+msgstr[0] "分支 (fork) "
+
+msgid "ForkedFromProjectPath|Forked from"
+msgstr "分支 (fork) 自"
 
 msgid "From issue creation until deploy to production"
-msgstr "從議題建立至線上部署"
+msgstr "從議題 (issue) 建立直到部署至營運環境"
 
 msgid "From merge request merge until deploy to production"
-msgstr "從請求被合併後至線上部署"
+msgstr "從請求被合併後 (merge request merged) 直到部署至營運環境"
+
+msgid "Go to your fork"
+msgstr "前往您的分支 (fork) "
+
+msgid "GoToYourFork|Fork"
+msgstr "前往您的分支 (fork) "
+
+msgid "Home"
+msgstr "首頁"
+
+msgid "Housekeeping successfully started"
+msgstr "已開始維護"
+
+msgid "Import repository"
+msgstr "匯入檔案庫 (repository)"
 
 msgid "Interval Pattern"
-msgstr ""
+msgstr "循環週期"
 
 msgid "Introducing Cycle Analytics"
 msgstr "週期分析簡介"
 
+msgid "LFSStatus|Disabled"
+msgstr "停用"
+
+msgid "LFSStatus|Enabled"
+msgstr "啟用"
+
 msgid "Last %d day"
 msgid_plural "Last %d days"
-msgstr[0] "最後 %d 天"
+msgstr[0] "最近 %d 天"
 
 msgid "Last Pipeline"
-msgstr ""
+msgstr "最新流水線 (pipeline) "
+
+msgid "Last Update"
+msgstr "最後更新"
+
+msgid "Last commit"
+msgstr "最後更動記錄 (commit) "
+
+msgid "Learn more in the"
+msgstr "了解更多"
+
+msgid "Learn more in the|pipeline schedules documentation"
+msgstr "流水線 (pipeline) 排程說明文件"
+
+msgid "Leave group"
+msgstr "退出群組"
+
+msgid "Leave project"
+msgstr "退出專案"
 
 msgid "Limited to showing %d event at most"
 msgid_plural "Limited to showing %d events at most"
-msgstr[0] "最多顯示 %d 個事件"
+msgstr[0] "限制最多顯示 %d 個事件"
 
 msgid "Median"
 msgstr "中位數"
 
+msgid "MissingSSHKeyWarningLink|add an SSH key"
+msgstr "新增 SSH 金鑰"
+
 msgid "New Issue"
 msgid_plural "New Issues"
-msgstr[0] "新議題"
+msgstr[0] "建立議題 (issue) "
 
 msgid "New Pipeline Schedule"
-msgstr ""
+msgstr "建立流水線 (pipeline) 排程"
+
+msgid "New branch"
+msgstr "新分支 (branch) "
+
+msgid "New directory"
+msgstr "新增目錄"
+
+msgid "New file"
+msgstr "新增檔案"
+
+msgid "New issue"
+msgstr "新增議題 (issue) "
+
+msgid "New merge request"
+msgstr "新增合併請求 (merge request) "
+
+msgid "New schedule"
+msgstr "新增排程"
+
+msgid "New snippet"
+msgstr "新文字片段"
+
+msgid "New tag"
+msgstr "新增標籤"
+
+msgid "No repository"
+msgstr "找不到檔案庫 (repository)"
 
 msgid "No schedules"
-msgstr ""
+msgstr "沒有排程"
 
 msgid "Not available"
 msgstr "無法使用"
@@ -130,135 +462,502 @@ msgstr "無法使用"
 msgid "Not enough data"
 msgstr "資料不足"
 
+msgid "Notification events"
+msgstr "事件通知"
+
+msgid "NotificationEvent|Close issue"
+msgstr "關閉議題 (issue) "
+
+msgid "NotificationEvent|Close merge request"
+msgstr "關閉合併請求 (merge request) "
+
+msgid "NotificationEvent|Failed pipeline"
+msgstr "流水線 (pipeline) 失敗"
+
+msgid "NotificationEvent|Merge merge request"
+msgstr "合併請求 (merge request) 被合併"
+
+msgid "NotificationEvent|New issue"
+msgstr "新增議題 (issue) "
+
+msgid "NotificationEvent|New merge request"
+msgstr "新增合併請求 (merge request) "
+
+msgid "NotificationEvent|New note"
+msgstr "新增評論"
+
+msgid "NotificationEvent|Reassign issue"
+msgstr "重新指派議題 (issue) "
+
+msgid "NotificationEvent|Reassign merge request"
+msgstr "重新指派合併請求 (merge request) "
+
+msgid "NotificationEvent|Reopen issue"
+msgstr "重啟議題 (issue)"
+
+msgid "NotificationEvent|Successful pipeline"
+msgstr "流水線 (pipeline) 成功完成"
+
+msgid "NotificationLevel|Custom"
+msgstr "自訂"
+
+msgid "NotificationLevel|Disabled"
+msgstr "停用"
+
+msgid "NotificationLevel|Global"
+msgstr "全域"
+
+msgid "NotificationLevel|On mention"
+msgstr "提及"
+
+msgid "NotificationLevel|Participate"
+msgstr "參與"
+
+msgid "NotificationLevel|Watch"
+msgstr "關注"
+
+msgid "OfSearchInADropdown|Filter"
+msgstr "篩選"
+
 msgid "OpenedNDaysAgo|Opened"
 msgstr "開始於"
 
+msgid "Options"
+msgstr "選項"
+
 msgid "Owner"
-msgstr ""
+msgstr "所有權"
+
+msgid "Pipeline"
+msgstr "流水線 (pipeline) "
 
 msgid "Pipeline Health"
-msgstr "流水線健康指標"
+msgstr "流水線 (pipeline) 健康指數"
 
 msgid "Pipeline Schedule"
-msgstr ""
+msgstr "流水線 (pipeline) 排程"
 
 msgid "Pipeline Schedules"
-msgstr ""
+msgstr "流水線 (pipeline) 排程"
 
 msgid "PipelineSchedules|Activated"
-msgstr ""
+msgstr "是否啟用"
 
 msgid "PipelineSchedules|Active"
-msgstr ""
+msgstr "已啟用"
 
 msgid "PipelineSchedules|All"
-msgstr ""
+msgstr "所有"
 
 msgid "PipelineSchedules|Inactive"
-msgstr ""
+msgstr "未啟用"
 
 msgid "PipelineSchedules|Next Run"
-msgstr ""
+msgstr "下次執行時間"
 
 msgid "PipelineSchedules|None"
-msgstr ""
+msgstr "ç„¡"
 
 msgid "PipelineSchedules|Provide a short description for this pipeline"
-msgstr ""
+msgstr "請簡單說明此流水線 (pipeline) "
 
 msgid "PipelineSchedules|Take ownership"
-msgstr ""
+msgstr "取得所有權"
 
 msgid "PipelineSchedules|Target"
-msgstr ""
+msgstr "目標"
+
+msgid "PipelineSheduleIntervalPattern|Custom"
+msgstr "自訂"
+
+msgid "Pipeline|with stage"
+msgstr "於階段"
+
+msgid "Pipeline|with stages"
+msgstr "於階段"
+
+msgid "Project '%{project_name}' queued for deletion."
+msgstr "專案 '%{project_name}' 已加入刪除佇列。"
+
+msgid "Project '%{project_name}' was successfully created."
+msgstr "專案 '%{project_name}' 建立完成。"
+
+msgid "Project '%{project_name}' was successfully updated."
+msgstr "專案 '%{project_name}' 更新完成。"
+
+msgid "Project '%{project_name}' will be deleted."
+msgstr "專案 '%{project_name}' 將被刪除。"
+
+msgid "Project access must be granted explicitly to each user."
+msgstr "專案權限必須一一指派給每個使用者。"
+
+msgid "Project export could not be deleted."
+msgstr "匯出的專案無法被刪除。"
+
+msgid "Project export has been deleted."
+msgstr "匯出的專案已被刪除。"
+
+msgid ""
+"Project export link has expired. Please generate a new export from your "
+"project settings."
+msgstr "專案的匯出連結已失效。請到專案設定中產生新的連結。"
+
+msgid "Project export started. A download link will be sent by email."
+msgstr "專案導出已開始。完成後下載連結會送到您的信箱。"
+
+msgid "Project home"
+msgstr "專案首頁"
+
+msgid "ProjectFeature|Disabled"
+msgstr "停用"
+
+msgid "ProjectFeature|Everyone with access"
+msgstr "任何人都可存取"
+
+msgid "ProjectFeature|Only team members"
+msgstr "只有團隊成員可以存取"
+
+msgid "ProjectFileTree|Name"
+msgstr "名稱"
+
+msgid "ProjectLastActivity|Never"
+msgstr "從未"
 
 msgid "ProjectLifecycle|Stage"
-msgstr "專案生命週期"
+msgstr "階段"
+
+msgid "ProjectNetworkGraph|Graph"
+msgstr "分支圖"
 
 msgid "Read more"
-msgstr "了解更多"
+msgstr "瞭解更多"
+
+msgid "Readme"
+msgstr "說明檔"
+
+msgid "RefSwitcher|Branches"
+msgstr "分支 (branch) "
+
+msgid "RefSwitcher|Tags"
+msgstr "標籤"
 
 msgid "Related Commits"
-msgstr "相關的送交"
+msgstr "相關的更動記錄 (commit) "
 
 msgid "Related Deployed Jobs"
 msgstr "相關的部署作業"
 
 msgid "Related Issues"
-msgstr "相關的議題"
+msgstr "相關的議題 (issue) "
 
 msgid "Related Jobs"
 msgstr "相關的作業"
 
 msgid "Related Merge Requests"
-msgstr "相關的合併請求"
+msgstr "相關的合併請求 (merge request) "
 
 msgid "Related Merged Requests"
 msgstr "相關已合併的請求"
 
+msgid "Remind later"
+msgstr "稍後提醒"
+
+msgid "Remove project"
+msgstr "刪除專案"
+
+msgid "Request Access"
+msgstr "申請權限"
+
+msgid "Revert this commit"
+msgstr "還原此更動記錄 (commit)"
+
+msgid "Revert this merge request"
+msgstr "還原此合併請求 (merge request) "
+
 msgid "Save pipeline schedule"
-msgstr ""
+msgstr "保存流水線 (pipeline) 排程"
 
 msgid "Schedule a new pipeline"
-msgstr ""
+msgstr "建立流水線 (pipeline) 排程"
+
+msgid "Scheduling Pipelines"
+msgstr "流水線 (pipeline) 計劃"
+
+msgid "Search branches and tags"
+msgstr "搜尋分支 (branch) 和標籤"
+
+msgid "Select Archive Format"
+msgstr "選擇下載格式"
 
 msgid "Select a timezone"
-msgstr ""
+msgstr "選擇時區"
 
 msgid "Select target branch"
-msgstr ""
+msgstr "選擇目標分支 (branch) "
+
+msgid "Set a password on your account to pull or push via %{protocol}"
+msgstr "請先設定密碼,才能使用 %{protocol} 來上傳 (push) 或下載 (pull) 。"
+
+msgid "Set up CI"
+msgstr "設定 CI"
+
+msgid "Set up Koding"
+msgstr "設定 Koding"
+
+msgid "Set up auto deploy"
+msgstr "設定自動部署"
+
+msgid "SetPasswordToCloneLink|set a password"
+msgstr "設定密碼"
 
 msgid "Showing %d event"
 msgid_plural "Showing %d events"
 msgstr[0] "顯示 %d 個事件"
 
+msgid "Source code"
+msgstr "原始碼"
+
+msgid "StarProject|Star"
+msgstr "收藏"
+
+msgid "Start a %{new_merge_request} with these changes"
+msgstr "以這些改動建立一個新的 %{new_merge_request} "
+
+msgid "Switch branch/tag"
+msgstr "切換分支 (branch) 或標籤"
+
+msgid "Tag"
+msgid_plural "Tags"
+msgstr[0] "標籤"
+
+msgid "Tags"
+msgstr "標籤"
+
 msgid "Target Branch"
-msgstr ""
+msgstr "目標分支 (branch) "
 
-msgid "The coding stage shows the time from the first commit to creating the merge request. The data will automatically be added here once you create your first merge request."
-msgstr "程式開發階段顯示從第一次送交到建立合併請求的時間。建立第一個合併請求後,資料將自動填入。"
+msgid ""
+"The coding stage shows the time from the first commit to creating the merge "
+"request. The data will automatically be added here once you create your "
+"first merge request."
+msgstr ""
+"程式開發階段顯示從第一次更動記錄 (commit) 到建立合併請求 (merge request) 的時間。建立第一個合併請求後,資料將自動填入。"
 
 msgid "The collection of events added to the data gathered for that stage."
-msgstr "與該階段相關的事件。"
+msgstr "該階段中的相關事件集合。"
+
+msgid "The fork relationship has been removed."
+msgstr "分支與主幹間的關聯 (fork relationship) 已被刪除。"
 
-msgid "The issue stage shows the time it takes from creating an issue to assigning the issue to a milestone, or add the issue to a list on your Issue Board. Begin creating issues to see data for this stage."
-msgstr "議題階段顯示從議題建立到設置里程碑、或將該議題加至議題看板的時間。建立第一個議題後,資料將自動填入。"
+msgid ""
+"The issue stage shows the time it takes from creating an issue to assigning "
+"the issue to a milestone, or add the issue to a list on your Issue Board. "
+"Begin creating issues to see data for this stage."
+msgstr ""
+"議題 (issue) 階段顯示從議題建立到設定里程碑所花的時間,或是議題被分類到議題看板 (issue board) "
+"中所花的時間。建立第一個議題後,資料將自動填入。"
 
 msgid "The phase of the development lifecycle."
-msgstr "專案開發生命週期的各個階段。"
+msgstr "專案開發週期的各個階段。"
 
-msgid "The planning stage shows the time from the previous step to pushing your first commit. This time will be added automatically once you push your first commit."
-msgstr "計劃階段所顯示的是議題被排程後至第一個送交被推送的時間。一旦完成(或執行)首次的推送,資料將自動填入。"
+msgid ""
+"The pipelines schedule runs pipelines in the future, repeatedly, for "
+"specific branches or tags. Those scheduled pipelines will inherit limited "
+"project access based on their associated user."
+msgstr ""
+"在指定了特定分支 (branch) 或標籤後,此處的流水線 (pipeline) 排程會不斷地重複執行。\n"
+"流水線排程的存取權限與專案本身相同。"
 
-msgid "The production stage shows the total time it takes between creating an issue and deploying the code to production. The data will be automatically added once you have completed the full idea to production cycle."
-msgstr "上線階段顯示從建立一個議題到部署程式至線上的總時間。當完成從想法到產品實現的循環後,資料將自動填入。"
+msgid ""
+"The planning stage shows the time from the previous step to pushing your "
+"first commit. This time will be added automatically once you push your first "
+"commit."
+msgstr "計劃階段顯示從更動記錄 (commit) 被排程至第一個推送的時間。第一次推送之後,資料將自動填入。"
+
+msgid ""
+"The production stage shows the total time it takes between creating an issue "
+"and deploying the code to production. The data will be automatically added "
+"once you have completed the full idea to production cycle."
+msgstr "營運階段顯示從建立議題 (issue) 到部署程式上線所花的時間。完成從發想到上線的完整開發週期後,資料將自動填入。"
 
-msgid "The review stage shows the time from creating the merge request to merging it. The data will automatically be added after you merge your first merge request."
-msgstr "複閱階段顯示從合併請求建立後至被合併的時間。當建立第一個合併請求後,資料將自動填入。"
+msgid "The project can be accessed by any logged in user."
+msgstr "本專案可讓任何已登入的使用者存取"
+
+msgid "The project can be accessed without any authentication."
+msgstr "本專案可讓任何人存取"
+
+msgid "The repository for this project does not exist."
+msgstr "本專案沒有檔案庫 (repository) "
+
+msgid ""
+"The review stage shows the time from creating the merge request to merging "
+"it. The data will automatically be added after you merge your first merge "
+"request."
+msgstr ""
+"複閱階段顯示從合併請求 (merge request) 建立後至被合併的時間。當建立第一個合併請求 (merge request) 後,資料將自動填入。"
 
-msgid "The staging stage shows the time between merging the MR and deploying code to the production environment. The data will be automatically added once you deploy to production for the first time."
-msgstr "預備階段顯示從合併請求被合併後至部署上線的時間。當第一次部署上線後,資料將自動填入。"
+msgid ""
+"The staging stage shows the time between merging the MR and deploying code "
+"to the production environment. The data will be automatically added once you "
+"deploy to production for the first time."
+msgstr "試營運段顯示從合併請求 (merge request) 被合併後至部署營運的時間。當第一次部署營運後,資料將自動填入"
 
-msgid "The testing stage shows the time GitLab CI takes to run every pipeline for the related merge request. The data will automatically be added after your first pipeline finishes running."
-msgstr "測試階段顯示相關合併請求的流水線所花的時間。當第一個流水線運作完畢後,資料將自動填入。"
+msgid ""
+"The testing stage shows the time GitLab CI takes to run every pipeline for "
+"the related merge request. The data will automatically be added after your "
+"first pipeline finishes running."
+msgstr ""
+"測試階段顯示相關合併請求 (merge request) 的流水線 (pipeline) 所花的時間。當第一個流水線 (pipeline) "
+"執行完畢後,資料將自動填入。"
 
 msgid "The time taken by each data entry gathered by that stage."
-msgstr "每筆該階段相關資料所花的時間。"
+msgstr "該階段中每一個資料項目所花的時間。"
 
-msgid "The value lying at the midpoint of a series of observed values. E.g., between 3, 5, 9, the median is 5. Between 3, 5, 7, 8, the median is (5+7)/2 = 6."
+msgid ""
+"The value lying at the midpoint of a series of observed values. E.g., "
+"between 3, 5, 9, the median is 5. Between 3, 5, 7, 8, the median is (5+7)/2 ="
+" 6."
 msgstr "中位數是一個數列中最中間的值。例如在 3、5、9 之間,中位數是 5。在 3、5、7、8 之間,中位數是 (5 + 7)/ 2 = 6。"
 
+msgid ""
+"This means you can not push code until you create an empty repository or "
+"import existing one."
+msgstr "這代表在您建立一個空的檔案庫 (repository) 或是匯入一個現存的檔案庫之前,您將無法上傳更新 (push) 。"
+
 msgid "Time before an issue gets scheduled"
-msgstr "議題等待排程的時間"
+msgstr "議題 (issue) 被列入日程表的時間"
 
 msgid "Time before an issue starts implementation"
-msgstr "議題等待開始實作的時間"
+msgstr "議題 (issue) 等待開始實作的時間"
 
 msgid "Time between merge request creation and merge/close"
-msgstr "合併請求被合併或是關閉的時間"
+msgstr "合併請求 (merge request) 從建立到被合併或是關閉的時間"
 
 msgid "Time until first merge request"
-msgstr "第一個合併請求被建立前的時間"
+msgstr "第一個合併請求 (merge request) 被建立前的時間"
+
+msgid "Timeago|%s days ago"
+msgstr " %s 天前"
+
+msgid "Timeago|%s days remaining"
+msgstr "剩下 %s 天"
+
+msgid "Timeago|%s hours remaining"
+msgstr "剩下 %s 小時"
+
+msgid "Timeago|%s minutes ago"
+msgstr " %s 分鐘前"
+
+msgid "Timeago|%s minutes remaining"
+msgstr "剩下 %s 分鐘"
+
+msgid "Timeago|%s months ago"
+msgstr " %s 個月前"
+
+msgid "Timeago|%s months remaining"
+msgstr "剩下 %s 月"
+
+msgid "Timeago|%s seconds remaining"
+msgstr "剩下 %s 秒"
+
+msgid "Timeago|%s weeks ago"
+msgstr " %s 週前"
+
+msgid "Timeago|%s weeks remaining"
+msgstr "剩下 %s 週"
+
+msgid "Timeago|%s years ago"
+msgstr " %s 年前"
+
+msgid "Timeago|%s years remaining"
+msgstr "剩下 %s 年"
+
+msgid "Timeago|1 day remaining"
+msgstr "剩下 1 天"
+
+msgid "Timeago|1 hour remaining"
+msgstr "剩下 1 小時"
+
+msgid "Timeago|1 minute remaining"
+msgstr "剩下 1 分鐘"
+
+msgid "Timeago|1 month remaining"
+msgstr "剩下 1 個月"
+
+msgid "Timeago|1 week remaining"
+msgstr "剩下 1 週"
+
+msgid "Timeago|1 year remaining"
+msgstr "剩下 1 年"
+
+msgid "Timeago|Past due"
+msgstr "逾期"
+
+msgid "Timeago|a day ago"
+msgstr " 1 天前"
+
+msgid "Timeago|a month ago"
+msgstr " 1 個月前"
+
+msgid "Timeago|a week ago"
+msgstr " 1 週前"
+
+msgid "Timeago|a while"
+msgstr "剛剛"
+
+msgid "Timeago|a year ago"
+msgstr " 1 年前"
+
+msgid "Timeago|about %s hours ago"
+msgstr "約 %s 小時前"
+
+msgid "Timeago|about a minute ago"
+msgstr "約 1 分鐘前"
+
+msgid "Timeago|about an hour ago"
+msgstr "約 1 小時前"
+
+msgid "Timeago|in %s days"
+msgstr " %s 天後"
+
+msgid "Timeago|in %s hours"
+msgstr " %s 小時後"
+
+msgid "Timeago|in %s minutes"
+msgstr " %s 分鐘後"
+
+msgid "Timeago|in %s months"
+msgstr " %s 個月後"
+
+msgid "Timeago|in %s seconds"
+msgstr " %s 秒後"
+
+msgid "Timeago|in %s weeks"
+msgstr " %s 週後"
+
+msgid "Timeago|in %s years"
+msgstr " %s 年後"
+
+msgid "Timeago|in 1 day"
+msgstr " 1 天後"
+
+msgid "Timeago|in 1 hour"
+msgstr " 1 小時後"
+
+msgid "Timeago|in 1 minute"
+msgstr " 1 分鐘後"
+
+msgid "Timeago|in 1 month"
+msgstr " 1 個月後"
+
+msgid "Timeago|in 1 week"
+msgstr " 1 週後"
+
+msgid "Timeago|in 1 year"
+msgstr " 1 年後"
+
+msgid "Timeago|less than a minute ago"
+msgstr "不到 1 分鐘前"
 
 msgid "Time|hr"
 msgid_plural "Time|hrs"
@@ -275,7 +974,28 @@ msgid "Total Time"
 msgstr "總時間"
 
 msgid "Total test time for all commits/merges"
-msgstr "所有送交和合併的總測試時間"
+msgstr "合併 (merge) 與更動記錄 (commit) 的總測試時間"
+
+msgid "Unstar"
+msgstr "取消收藏"
+
+msgid "Upload New File"
+msgstr "上傳新檔案"
+
+msgid "Upload file"
+msgstr "上傳檔案"
+
+msgid "Use your global notification setting"
+msgstr "使用全域通知設定"
+
+msgid "VisibilityLevel|Internal"
+msgstr "內部"
+
+msgid "VisibilityLevel|Private"
+msgstr "私有"
+
+msgid "VisibilityLevel|Public"
+msgstr "公開"
 
 msgid "Want to see the data? Please ask an administrator for access."
 msgstr "權限不足。如需查看相關資料,請向管理員申請權限。"
@@ -283,12 +1003,85 @@ msgstr "權限不足。如需查看相關資料,請向管理員申請權限。
 msgid "We don't have enough data to show this stage."
 msgstr "因該階段的資料不足而無法顯示相關資訊"
 
-msgid "You have reached your project limit"
+msgid "Withdraw Access Request"
+msgstr "取消權限申請"
+
+msgid ""
+"You are going to remove %{project_name_with_namespace}.\n"
+"Removed project CANNOT be restored!\n"
+"Are you ABSOLUTELY sure?"
 msgstr ""
+"即將要刪除 %{project_name_with_namespace}。\n"
+"被刪除的專案完全無法救回來喔!\n"
+"真的「100%確定」要這麼做嗎?"
+
+msgid ""
+"You are going to remove the fork relationship to source project "
+"%{forked_from_project}. Are you ABSOLUTELY sure?"
+msgstr ""
+"將要刪除本分支專案與主幹的所有關聯 (fork relationship) 。 %{forked_from_project} "
+"真的「100%確定」要這麼做嗎?"
+
+msgid ""
+"You are going to transfer %{project_name_with_namespace} to another owner. "
+"Are you ABSOLUTELY sure?"
+msgstr "將要把 %{project_name_with_namespace} 的所有權轉移給另一個人。真的「100%確定」要這麼做嗎?"
+
+msgid "You can only add files when you are on a branch"
+msgstr "只能在分支 (branch) 上建立檔案"
+
+msgid "You have reached your project limit"
+msgstr "您已達到專案數量限制"
+
+msgid "You must sign in to star a project"
+msgstr "必須登入才能收藏專案"
 
 msgid "You need permission."
-msgstr "您需要相關的權限。"
+msgstr "需要權限才能這麼做。"
+
+msgid "You will not get any notifications via email"
+msgstr "不會收到任何通知郵件"
+
+msgid "You will only receive notifications for the events you choose"
+msgstr "只接收您選擇的事件通知"
+
+msgid ""
+"You will only receive notifications for threads you have participated in"
+msgstr "只接收參與主題的通知"
+
+msgid "You will receive notifications for any activity"
+msgstr "接收所有活動的通知"
+
+msgid ""
+"You will receive notifications only for comments in which you were "
+"@mentioned"
+msgstr "只接收評論中提及(@)您的通知"
+
+msgid ""
+"You won't be able to pull or push project code via %{protocol} until you "
+"%{set_password_link} on your account"
+msgstr ""
+"在帳號上 %{set_password_link} 之前, 將無法使用 %{protocol} 上傳 (push) 或下載 (pull) 程式碼。"
+
+msgid ""
+"You won't be able to pull or push project code via SSH until you "
+"%{add_ssh_key_link} to your profile"
+msgstr "在個人帳號中 %{add_ssh_key_link} 之前, 將無法使用 SSH 上傳 (push) 或下載 (pull) 程式碼。"
+
+msgid "Your name"
+msgstr "您的名字"
 
 msgid "day"
 msgid_plural "days"
 msgstr[0] "天"
+
+msgid "new merge request"
+msgstr "建立合併請求"
+
+msgid "notification emails"
+msgstr "通知信"
+
+msgid "parent"
+msgid_plural "parents"
+msgstr[0] "上層"
+
diff --git a/package.json b/package.json
index 045f07ee2f9970a0ee8aae97e2ea084aa854633c..5a997e813f8d032dad368e1116674ab64489f4b5 100644
--- a/package.json
+++ b/package.json
@@ -13,6 +13,7 @@
   },
   "dependencies": {
     "babel-core": "^6.22.1",
+    "babel-eslint": "^7.2.1",
     "babel-loader": "^6.2.10",
     "babel-plugin-transform-define": "^1.2.0",
     "babel-preset-latest": "^6.24.0",
diff --git a/spec/controllers/groups/milestones_controller_spec.rb b/spec/controllers/groups/milestones_controller_spec.rb
index f3263bc177d4db75f851df34fe2e011ffda6ffaa..c6e5fb61cf9f8bfd4660414fd21f510c07196884 100644
--- a/spec/controllers/groups/milestones_controller_spec.rb
+++ b/spec/controllers/groups/milestones_controller_spec.rb
@@ -23,6 +23,21 @@ describe Groups::MilestonesController do
     project.team << [user, :master]
   end
 
+  describe "#index" do
+    it 'shows group milestones page' do
+      get :index, group_id: group.to_param
+
+      expect(response).to have_http_status(200)
+    end
+
+    it 'shows group milestones JSON' do
+      get :index, group_id: group.to_param, format: :json
+
+      expect(response).to have_http_status(200)
+      expect(response.content_type).to eq 'application/json'
+    end
+  end
+
   it_behaves_like 'milestone tabs'
 
   describe "#create" do
diff --git a/spec/factories/projects.rb b/spec/factories/projects.rb
index aef1c17a2394a902bdd3c749704f6da6d5ce1ede..1bb2db11e7f1f89466b7751e04a5e2a4ad548353 100644
--- a/spec/factories/projects.rb
+++ b/spec/factories/projects.rb
@@ -220,7 +220,7 @@ FactoryGirl.define do
         active: true,
         properties: {
           'project_url' => 'http://redmine/projects/project_name_in_redmine',
-          'issues_url' => "http://redmine/#{project.id}/project_name_in_redmine/:id",
+          'issues_url' => 'http://redmine/projects/project_name_in_redmine/issues/:id',
           'new_issue_url' => 'http://redmine/projects/project_name_in_redmine/issues/new'
         }
       )
diff --git a/spec/features/boards/sidebar_spec.rb b/spec/features/boards/sidebar_spec.rb
index 301c243febd448917896b53694be13365a768180..1c9595def217c8cde91ce980c67c9f71f1c28d76 100644
--- a/spec/features/boards/sidebar_spec.rb
+++ b/spec/features/boards/sidebar_spec.rb
@@ -79,6 +79,22 @@ describe 'Issue Boards', feature: true, js: true do
     end
   end
 
+  it 'does not show remove button for backlog or closed issues' do
+    create(:issue, project: project)
+    create(:issue, :closed, project: project)
+
+    visit namespace_project_board_path(project.namespace, project, board)
+    wait_for_requests
+
+    click_card(find('.board:nth-child(1)').first('.card'))
+
+    expect(find('.issue-boards-sidebar')).not_to have_button 'Remove from board'
+
+    click_card(find('.board:nth-child(3)').first('.card'))
+
+    expect(find('.issue-boards-sidebar')).not_to have_button 'Remove from board'
+  end
+
   context 'assignee' do
     it 'updates the issues assignee' do
       click_card(card)
diff --git a/spec/features/dashboard/projects_spec.rb b/spec/features/dashboard/projects_spec.rb
index f29186f368df749082fc9ccd962a58428ade1994..e9ef5d7983a8bb5cadf596cec9d2d2e3e70b0fd3 100644
--- a/spec/features/dashboard/projects_spec.rb
+++ b/spec/features/dashboard/projects_spec.rb
@@ -1,8 +1,8 @@
 require 'spec_helper'
 
-RSpec.describe 'Dashboard Projects', feature: true do
+feature 'Dashboard Projects' do
   let(:user) { create(:user) }
-  let(:project) { create(:project, name: "awesome stuff") }
+  let(:project) { create(:project, name: 'awesome stuff') }
   let(:project2) { create(:project, :public, name: 'Community project') }
 
   before do
@@ -15,6 +15,14 @@ RSpec.describe 'Dashboard Projects', feature: true do
     expect(page).to have_content('awesome stuff')
   end
 
+  it 'shows "New project" button' do
+    visit dashboard_projects_path
+
+    page.within '#content-body' do
+      expect(page).to have_link('New project')
+    end
+  end
+
   context 'when last_repository_updated_at, last_activity_at and update_at are present' do
     it 'shows the last_repository_updated_at attribute as the update date' do
       project.update_attributes!(last_repository_updated_at: Time.now, last_activity_at: 1.hour.ago)
@@ -47,8 +55,8 @@ RSpec.describe 'Dashboard Projects', feature: true do
     end
   end
 
-  describe "with a pipeline", redis: true do
-    let!(:pipeline) {  create(:ci_pipeline, project: project, sha: project.commit.sha) }
+  describe 'with a pipeline', redis: true do
+    let(:pipeline) { create(:ci_pipeline, project: project, sha: project.commit.sha) }
 
     before do
       # Since the cache isn't updated when a new pipeline is created
diff --git a/spec/features/expand_collapse_diffs_spec.rb b/spec/features/expand_collapse_diffs_spec.rb
index ea749528c1172e6397a1412c0c65c31fe26d5319..d492a15ea171ba6dcb12af9ab0afe3f97c78758a 100644
--- a/spec/features/expand_collapse_diffs_spec.rb
+++ b/spec/features/expand_collapse_diffs_spec.rb
@@ -129,7 +129,7 @@ feature 'Expand and collapse diffs', js: true, feature: true do
 
         before do
           large_diff.find('.diff-line-num', match: :prefer_exact).hover
-          large_diff.find('.add-diff-note').click
+          large_diff.find('.add-diff-note', match: :prefer_exact).click
           large_diff.find('.note-textarea').send_keys comment_text
           large_diff.find_button('Comment').click
           wait_for_requests
diff --git a/spec/features/issuables/user_sees_sidebar_spec.rb b/spec/features/issuables/user_sees_sidebar_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..4d7a7dc180616bf93472b571a1064fd764b883e1
--- /dev/null
+++ b/spec/features/issuables/user_sees_sidebar_spec.rb
@@ -0,0 +1,30 @@
+require 'rails_helper'
+
+describe 'Issue Sidebar on Mobile' do
+  include MobileHelpers
+
+  let(:project) { create(:project, :public) }
+  let(:merge_request) { create(:merge_request, source_project: project) }
+  let(:issue) { create(:issue, project: project) }
+  let!(:user) { create(:user)}
+
+  before do
+    sign_in(user)
+  end
+
+  context 'mobile sidebar on merge requests', js: true do
+    before do
+      visit namespace_project_merge_request_path(merge_request.project.namespace, merge_request.project, merge_request)
+    end
+
+    it_behaves_like "issue sidebar stays collapsed on mobile"
+  end
+
+  context 'mobile sidebar on issues', js: true do
+    before do
+      visit namespace_project_issue_path(project.namespace, project, issue)
+    end
+
+    it_behaves_like "issue sidebar stays collapsed on mobile"
+  end
+end
diff --git a/spec/features/issues/filtered_search/filter_issues_spec.rb b/spec/features/issues/filtered_search/filter_issues_spec.rb
index 863f8f75cd80fc4cfcff9d34995509df5c453cc6..4cb728cc82b97d73e4f5ea119ac4ad0b2d01932f 100644
--- a/spec/features/issues/filtered_search/filter_issues_spec.rb
+++ b/spec/features/issues/filtered_search/filter_issues_spec.rb
@@ -459,7 +459,7 @@ describe 'Filter issues', js: true, feature: true do
 
     context 'issue label clicked' do
       before do
-        find('.issues-list .issue .issue-info a .label', text: multiple_words_label.title).click
+        find('.issues-list .issue .issue-main-info .issuable-info a .label', text: multiple_words_label.title).click
       end
 
       it 'filters' do
diff --git a/spec/features/issues/issue_sidebar_spec.rb b/spec/features/issues/issue_sidebar_spec.rb
index 163bc4bb32f0f9963ce2f0c6f2a02d9bb8857513..09724781a27e000ceecda538fb3da9fc88c047b3 100644
--- a/spec/features/issues/issue_sidebar_spec.rb
+++ b/spec/features/issues/issue_sidebar_spec.rb
@@ -154,20 +154,6 @@ feature 'Issue Sidebar', feature: true do
     end
   end
 
-  context 'as a allowed mobile user', js: true do
-    before do
-      project.team << [user, :developer]
-      resize_screen_xs
-      visit_issue(project, issue)
-    end
-
-    context 'mobile sidebar' do
-      it 'collapses the sidebar for small screens' do
-        expect(page).not_to have_css('aside.right-sidebar.right-sidebar-collapsed')
-      end
-    end
-  end
-
   context 'as a guest' do
     before do
       project.team << [user, :guest]
diff --git a/spec/features/issues/update_issues_spec.rb b/spec/features/issues/update_issues_spec.rb
index dc981406e4ebcdb91efc7f910da178f5b54e1671..df704b558393a49ebe7b4658c2311abd6802fcee 100644
--- a/spec/features/issues/update_issues_spec.rb
+++ b/spec/features/issues/update_issues_spec.rb
@@ -1,16 +1,16 @@
 require 'rails_helper'
 
-feature 'Multiple issue updating from issues#index', feature: true do
+feature 'Multiple issue updating from issues#index', :js do
   let!(:project)   { create(:project) }
   let!(:issue)     { create(:issue, project: project) }
   let!(:user)      { create(:user)}
 
   before do
     project.team << [user, :master]
-    gitlab_sign_in(user)
+    sign_in(user)
   end
 
-  context 'status', js: true do
+  context 'status' do
     it 'sets to closed' do
       visit namespace_project_issues_path(project.namespace, project)
 
@@ -37,7 +37,7 @@ feature 'Multiple issue updating from issues#index', feature: true do
     end
   end
 
-  context 'assignee', js: true do
+  context 'assignee' do
     it 'updates to current user' do
       visit namespace_project_issues_path(project.namespace, project)
 
@@ -67,8 +67,8 @@ feature 'Multiple issue updating from issues#index', feature: true do
     end
   end
 
-  context 'milestone', js: true do
-    let(:milestone)  { create(:milestone, project: project) }
+  context 'milestone' do
+    let!(:milestone) { create(:milestone, project: project) }
 
     it 'updates milestone' do
       visit namespace_project_issues_path(project.namespace, project)
diff --git a/spec/features/projects/import_export/import_file_spec.rb b/spec/features/projects/import_export/import_file_spec.rb
index a111aa87c52c41086bf9c05f1d653e07c79ebc43..3f8d225529800f4b7b8619e1c2ddb47d3d5e68af 100644
--- a/spec/features/projects/import_export/import_file_spec.rb
+++ b/spec/features/projects/import_export/import_file_spec.rb
@@ -98,6 +98,6 @@ feature 'Import/Export - project import integration test', feature: true, js: tr
   end
 
   def project_hook_exists?(project)
-    Gitlab::Git::Hook.new('post-receive', project.repository.path).exists?
+    Gitlab::Git::Hook.new('post-receive', project).exists?
   end
 end
diff --git a/spec/features/projects/new_project_spec.rb b/spec/features/projects/new_project_spec.rb
index 37d9a97033bca85b9229ab51e384f54bc8d22e00..22fb122373983a00f60fb9049c2b6d4274e9c20d 100644
--- a/spec/features/projects/new_project_spec.rb
+++ b/spec/features/projects/new_project_spec.rb
@@ -1,13 +1,27 @@
-require "spec_helper"
+require 'spec_helper'
 
-feature "New project", feature: true do
+feature 'New project' do
   let(:user) { create(:admin) }
 
   before do
-    gitlab_sign_in(user)
+    sign_in(user)
   end
 
-  context "Visibility level selector" do
+  it 'shows "New project" page' do
+    visit new_project_path
+
+    expect(page).to have_content('Project path')
+    expect(page).to have_content('Project name')
+
+    expect(page).to have_link('GitHub')
+    expect(page).to have_link('Bitbucket')
+    expect(page).to have_link('GitLab.com')
+    expect(page).to have_link('Google Code')
+    expect(page).to have_button('Repo by URL')
+    expect(page).to have_link('GitLab export')
+  end
+
+  context 'Visibility level selector' do
     Gitlab::VisibilityLevel.options.each do |key, level|
       it "sets selector to #{key}" do
         stub_application_setting(default_project_visibility: level)
@@ -28,20 +42,20 @@ feature "New project", feature: true do
     end
   end
 
-  context "Namespace selector" do
-    context "with user namespace" do
+  context 'Namespace selector' do
+    context 'with user namespace' do
       before do
         visit new_project_path
       end
 
-      it "selects the user namespace" do
-        namespace = find("#project_namespace_id")
+      it 'selects the user namespace' do
+        namespace = find('#project_namespace_id')
 
         expect(namespace.text).to eq user.username
       end
     end
 
-    context "with group namespace" do
+    context 'with group namespace' do
       let(:group) { create(:group, :private, owner: user) }
 
       before do
@@ -49,13 +63,13 @@ feature "New project", feature: true do
         visit new_project_path(namespace_id: group.id)
       end
 
-      it "selects the group namespace" do
-        namespace = find("#project_namespace_id option[selected]")
+      it 'selects the group namespace' do
+        namespace = find('#project_namespace_id option[selected]')
 
         expect(namespace.text).to eq group.name
       end
 
-      context "on validation error" do
+      context 'on validation error' do
         before do
           fill_in('project_path', with: 'private-group-project')
           choose('Internal')
@@ -64,15 +78,15 @@ feature "New project", feature: true do
           expect(page).to have_css '.project-edit-errors .alert.alert-danger'
         end
 
-        it "selects the group namespace" do
-          namespace = find("#project_namespace_id option[selected]")
+        it 'selects the group namespace' do
+          namespace = find('#project_namespace_id option[selected]')
 
           expect(namespace.text).to eq group.name
         end
       end
     end
 
-    context "with subgroup namespace" do
+    context 'with subgroup namespace' do
       let(:group) { create(:group, :private, owner: user) }
       let(:subgroup) { create(:group, parent: group) }
 
@@ -81,8 +95,8 @@ feature "New project", feature: true do
         visit new_project_path(namespace_id: subgroup.id)
       end
 
-      it "selects the group namespace" do
-        namespace = find("#project_namespace_id option[selected]")
+      it 'selects the group namespace' do
+        namespace = find('#project_namespace_id option[selected]')
 
         expect(namespace.text).to eq subgroup.full_path
       end
@@ -94,10 +108,45 @@ feature "New project", feature: true do
       visit new_project_path
     end
 
-    it 'does not autocomplete sensitive git repo URL' do
-      autocomplete = find('#project_import_url')['autocomplete']
+    context 'from git repository url' do
+      before do
+        first('.import_git').click
+      end
+
+      it 'does not autocomplete sensitive git repo URL' do
+        autocomplete = find('#project_import_url')['autocomplete']
+
+        expect(autocomplete).to eq('off')
+      end
+
+      it 'shows import instructions' do
+        git_import_instructions = first('.js-toggle-content')
 
-      expect(autocomplete).to eq('off')
+        expect(git_import_instructions).to be_visible
+        expect(git_import_instructions).to have_content 'Git repository URL'
+      end
+    end
+
+    context 'from GitHub' do
+      before do
+        first('.import_github').click
+      end
+
+      it 'shows import instructions' do
+        expect(page).to have_content('Import Projects from GitHub')
+        expect(current_path).to eq new_import_github_path
+      end
+    end
+
+    context 'from Google Code' do
+      before do
+        first('.import_google_code').click
+      end
+
+      it 'shows import instructions' do
+        expect(page).to have_content('Import projects from Google Code')
+        expect(current_path).to eq new_import_google_code_path
+      end
     end
   end
 end
diff --git a/spec/features/user_can_display_performance_bar_spec.rb b/spec/features/user_can_display_performance_bar_spec.rb
index 1bd7e0389393c20dcb783b4e0ef009133f822d8a..24fff1a3052c770cd7fb24cfada096c044973d4b 100644
--- a/spec/features/user_can_display_performance_bar_spec.rb
+++ b/spec/features/user_can_display_performance_bar_spec.rb
@@ -1,6 +1,6 @@
 require 'rails_helper'
 
-describe 'User can display performacne bar', :js do
+describe 'User can display performance bar', :js do
   shared_examples 'performance bar is disabled' do
     it 'does not show the performance bar by default' do
       expect(page).not_to have_css('#peek')
@@ -27,8 +27,8 @@ describe 'User can display performacne bar', :js do
         find('body').native.send_keys('pb')
       end
 
-      it 'does not show the performance bar by default' do
-        expect(page).not_to have_css('#peek')
+      it 'shows the performance bar' do
+        expect(page).to have_css('#peek')
       end
     end
   end
diff --git a/spec/finders/issues_finder_spec.rb b/spec/finders/issues_finder_spec.rb
index 8ace1fb57514f56c61c0240f4b6c40f7bced11e6..4a52f0d5c58e33481e44bc15698154049bfcb164 100644
--- a/spec/finders/issues_finder_spec.rb
+++ b/spec/finders/issues_finder_spec.rb
@@ -295,22 +295,121 @@ describe IssuesFinder do
     end
   end
 
-  describe '.not_restricted_by_confidentiality' do
-    let(:authorized_user) { create(:user) }
-    let(:project) { create(:empty_project, namespace: authorized_user.namespace) }
-    let!(:public_issue) { create(:issue, project: project) }
-    let!(:confidential_issue) { create(:issue, project: project, confidential: true) }
-
-    it 'returns non confidential issues for nil user' do
-      expect(described_class.send(:not_restricted_by_confidentiality, nil)).to include(public_issue)
-    end
+  describe '#with_confidentiality_access_check' do
+    let(:guest) { create(:user) }
+    set(:authorized_user) { create(:user) }
+    set(:project) { create(:empty_project, namespace: authorized_user.namespace) }
+    set(:public_issue) { create(:issue, project: project) }
+    set(:confidential_issue) { create(:issue, project: project, confidential: true) }
+
+    context 'when no project filter is given' do
+      let(:params) { {} }
+
+      context 'for an anonymous user' do
+        subject { described_class.new(nil, params).with_confidentiality_access_check }
+
+        it 'returns only public issues' do
+          expect(subject).to include(public_issue)
+          expect(subject).not_to include(confidential_issue)
+        end
+      end
+
+      context 'for a user without project membership' do
+        subject { described_class.new(user, params).with_confidentiality_access_check }
+
+        it 'returns only public issues' do
+          expect(subject).to include(public_issue)
+          expect(subject).not_to include(confidential_issue)
+        end
+      end
+
+      context 'for a guest user' do
+        subject { described_class.new(guest, params).with_confidentiality_access_check }
+
+        before do
+          project.add_guest(guest)
+        end
+
+        it 'returns only public issues' do
+          expect(subject).to include(public_issue)
+          expect(subject).not_to include(confidential_issue)
+        end
+      end
+
+      context 'for a project member with access to view confidential issues' do
+        subject { described_class.new(authorized_user, params).with_confidentiality_access_check }
 
-    it 'returns non confidential issues for user not authorized for the issues projects' do
-      expect(described_class.send(:not_restricted_by_confidentiality, user)).to include(public_issue)
+        it 'returns all issues' do
+          expect(subject).to include(public_issue, confidential_issue)
+        end
+      end
     end
 
-    it 'returns all issues for user authorized for the issues projects' do
-      expect(described_class.send(:not_restricted_by_confidentiality, authorized_user)).to include(public_issue, confidential_issue)
+    context 'when searching within a specific project' do
+      let(:params) { { project_id: project.id } }
+
+      context 'for an anonymous user' do
+        subject { described_class.new(nil, params).with_confidentiality_access_check }
+
+        it 'returns only public issues' do
+          expect(subject).to include(public_issue)
+          expect(subject).not_to include(confidential_issue)
+        end
+
+        it 'does not filter by confidentiality' do
+          expect(Issue).not_to receive(:where).with(a_string_matching('confidential'), anything)
+
+          subject
+        end
+      end
+
+      context 'for a user without project membership' do
+        subject { described_class.new(user, params).with_confidentiality_access_check }
+
+        it 'returns only public issues' do
+          expect(subject).to include(public_issue)
+          expect(subject).not_to include(confidential_issue)
+        end
+
+        it 'filters by confidentiality' do
+          expect(Issue).to receive(:where).with(a_string_matching('confidential'), anything)
+
+          subject
+        end
+      end
+
+      context 'for a guest user' do
+        subject { described_class.new(guest, params).with_confidentiality_access_check }
+
+        before do
+          project.add_guest(guest)
+        end
+
+        it 'returns only public issues' do
+          expect(subject).to include(public_issue)
+          expect(subject).not_to include(confidential_issue)
+        end
+
+        it 'filters by confidentiality' do
+          expect(Issue).to receive(:where).with(a_string_matching('confidential'), anything)
+
+          subject
+        end
+      end
+
+      context 'for a project member with access to view confidential issues' do
+        subject { described_class.new(authorized_user, params).with_confidentiality_access_check }
+
+        it 'returns all issues' do
+          expect(subject).to include(public_issue, confidential_issue)
+        end
+
+        it 'does not filter by confidentiality' do
+          expect(Issue).not_to receive(:where).with(a_string_matching('confidential'), anything)
+
+          subject
+        end
+      end
     end
   end
 end
diff --git a/spec/helpers/groups_helper_spec.rb b/spec/helpers/groups_helper_spec.rb
index 8da22dc78fa8f8f07de8a188128f6554ac9405d2..e3f9d9db9eb844fdbad06d7c3dad3e11534e92e3 100644
--- a/spec/helpers/groups_helper_spec.rb
+++ b/spec/helpers/groups_helper_spec.rb
@@ -91,7 +91,7 @@ describe GroupsHelper do
     let!(:very_deep_nested_group) { create(:group, parent: deep_nested_group) }
 
     it 'outputs the groups in the correct order' do
-      expect(group_title(very_deep_nested_group)).to match(/>#{group.name}<\/a>.*>#{nested_group.name}<\/a>.*>#{deep_nested_group.name}<\/a>/)
+      expect(helper.group_title(very_deep_nested_group)).to match(/>#{group.name}<\/a>.*>#{nested_group.name}<\/a>.*>#{deep_nested_group.name}<\/a>/)
     end
   end
 end
diff --git a/spec/helpers/issuables_helper_spec.rb b/spec/helpers/issuables_helper_spec.rb
index 15cb620199dfa5cfeeff368d896ad3e45ef13501..d2e918ef0141aa0c9eba4d5702c0b03f206bd1af 100644
--- a/spec/helpers/issuables_helper_spec.rb
+++ b/spec/helpers/issuables_helper_spec.rb
@@ -77,54 +77,89 @@ describe IssuablesHelper do
         }.with_indifferent_access
       end
 
+      let(:issues_finder) { IssuesFinder.new(nil, params) }
+      let(:merge_requests_finder) { MergeRequestsFinder.new(nil, params) }
+
+      before do
+        allow(helper).to receive(:issues_finder).and_return(issues_finder)
+        allow(helper).to receive(:merge_requests_finder).and_return(merge_requests_finder)
+      end
+
       it 'returns the cached value when called for the same issuable type & with the same params' do
-        expect(helper).to receive(:params).twice.and_return(params)
-        expect(helper).to receive(:issuables_count_for_state).with(:issues, :opened).and_return(42)
+        expect(issues_finder).to receive(:count_by_state).and_return(opened: 42)
 
         expect(helper.issuables_state_counter_text(:issues, :opened))
           .to eq('<span>Open</span> <span class="badge">42</span>')
 
-        expect(helper).not_to receive(:issuables_count_for_state)
+        expect(issues_finder).not_to receive(:count_by_state)
 
         expect(helper.issuables_state_counter_text(:issues, :opened))
           .to eq('<span>Open</span> <span class="badge">42</span>')
       end
 
+      it 'takes confidential status into account when searching for issues' do
+        expect(issues_finder).to receive(:count_by_state).and_return(opened: 42)
+
+        expect(helper.issuables_state_counter_text(:issues, :opened))
+          .to include('42')
+
+        expect(issues_finder).to receive(:user_cannot_see_confidential_issues?).twice.and_return(false)
+        expect(issues_finder).to receive(:count_by_state).and_return(opened: 40)
+
+        expect(helper.issuables_state_counter_text(:issues, :opened))
+          .to include('40')
+
+        expect(issues_finder).to receive(:user_can_see_all_confidential_issues?).and_return(true)
+        expect(issues_finder).to receive(:count_by_state).and_return(opened: 45)
+
+        expect(helper.issuables_state_counter_text(:issues, :opened))
+          .to include('45')
+      end
+
+      it 'does not take confidential status into account when searching for merge requests' do
+        expect(merge_requests_finder).to receive(:count_by_state).and_return(opened: 42)
+        expect(merge_requests_finder).not_to receive(:user_cannot_see_confidential_issues?)
+        expect(merge_requests_finder).not_to receive(:user_can_see_all_confidential_issues?)
+
+        expect(helper.issuables_state_counter_text(:merge_requests, :opened))
+          .to include('42')
+      end
+
       it 'does not take some keys into account in the cache key' do
-        expect(helper).to receive(:params).and_return({
+        expect(issues_finder).to receive(:count_by_state).and_return(opened: 42)
+        expect(issues_finder).to receive(:params).and_return({
           author_id: '11',
           state: 'foo',
           sort: 'foo',
           utf8: 'foo',
           page: 'foo'
         }.with_indifferent_access)
-        expect(helper).to receive(:issuables_count_for_state).with(:issues, :opened).and_return(42)
 
         expect(helper.issuables_state_counter_text(:issues, :opened))
           .to eq('<span>Open</span> <span class="badge">42</span>')
 
-        expect(helper).to receive(:params).and_return({
+        expect(issues_finder).not_to receive(:count_by_state)
+        expect(issues_finder).to receive(:params).and_return({
           author_id: '11',
           state: 'bar',
           sort: 'bar',
           utf8: 'bar',
           page: 'bar'
         }.with_indifferent_access)
-        expect(helper).not_to receive(:issuables_count_for_state)
 
         expect(helper.issuables_state_counter_text(:issues, :opened))
           .to eq('<span>Open</span> <span class="badge">42</span>')
       end
 
       it 'does not take params order into account in the cache key' do
-        expect(helper).to receive(:params).and_return('author_id' => '11', 'state' => 'opened')
-        expect(helper).to receive(:issuables_count_for_state).with(:issues, :opened).and_return(42)
+        expect(issues_finder).to receive(:params).and_return('author_id' => '11', 'state' => 'opened')
+        expect(issues_finder).to receive(:count_by_state).and_return(opened: 42)
 
         expect(helper.issuables_state_counter_text(:issues, :opened))
           .to eq('<span>Open</span> <span class="badge">42</span>')
 
-        expect(helper).to receive(:params).and_return('state' => 'opened', 'author_id' => '11')
-        expect(helper).not_to receive(:issuables_count_for_state)
+        expect(issues_finder).to receive(:params).and_return('state' => 'opened', 'author_id' => '11')
+        expect(issues_finder).not_to receive(:count_by_state)
 
         expect(helper.issuables_state_counter_text(:issues, :opened))
           .to eq('<span>Open</span> <span class="badge">42</span>')
diff --git a/spec/helpers/milestones_helper_spec.rb b/spec/helpers/milestones_helper_spec.rb
index 3cb809d42b52f357ecca23e86336e2522cd2f213..24d4f1b49384128f10643341d90af939ae1f288b 100644
--- a/spec/helpers/milestones_helper_spec.rb
+++ b/spec/helpers/milestones_helper_spec.rb
@@ -1,6 +1,42 @@
 require 'spec_helper'
 
 describe MilestonesHelper do
+  describe '#milestones_filter_dropdown_path' do
+    let(:project) { create(:empty_project) }
+    let(:project2) { create(:empty_project) }
+    let(:group) { create(:group) }
+
+    context 'when @project present' do
+      it 'returns project milestones JSON URL' do
+        assign(:project, project)
+
+        expect(helper.milestones_filter_dropdown_path).to eq(namespace_project_milestones_path(project.namespace, project, :json))
+      end
+    end
+
+    context 'when @target_project present' do
+      it 'returns targeted project milestones JSON URL' do
+        assign(:target_project, project2)
+
+        expect(helper.milestones_filter_dropdown_path).to eq(namespace_project_milestones_path(project2.namespace, project2, :json))
+      end
+    end
+
+    context 'when @group present' do
+      it 'returns group milestones JSON URL' do
+        assign(:group, group)
+
+        expect(helper.milestones_filter_dropdown_path).to eq(group_milestones_path(group, :json))
+      end
+    end
+
+    context 'when neither of @project/@target_project/@group present' do
+      it 'returns dashboard milestones JSON URL' do
+        expect(helper.milestones_filter_dropdown_path).to eq(dashboard_milestones_path(:json))
+      end
+    end
+  end
+
   describe "#milestone_date_range" do
     def result_for(*args)
       milestone_date_range(build(:milestone, *args))
diff --git a/spec/javascripts/awards_handler_spec.js b/spec/javascripts/awards_handler_spec.js
index 3fc03324d1669073d533f2733d7d055f734c1385..8e0568821086d5292f6b399332de65517c940990 100644
--- a/spec/javascripts/awards_handler_spec.js
+++ b/spec/javascripts/awards_handler_spec.js
@@ -1,7 +1,7 @@
 /* eslint-disable space-before-function-paren, no-var, one-var, one-var-declaration-per-line, no-unused-expressions, comma-dangle, new-parens, no-unused-vars, quotes, jasmine/no-spec-dupes, prefer-template, max-len */
 
 import Cookies from 'js-cookie';
-import AwardsHandler from '~/awards_handler';
+import loadAwardsHandler from '~/awards_handler';
 
 import '~/lib/utils/common_utils';
 
@@ -26,14 +26,13 @@ import '~/lib/utils/common_utils';
 
   describe('AwardsHandler', function() {
     preloadFixtures('issues/issue_with_comment.html.raw');
-    beforeEach(function() {
+    beforeEach(function(done) {
       loadFixtures('issues/issue_with_comment.html.raw');
-      awardsHandler = new AwardsHandler;
-      spyOn(awardsHandler, 'postEmoji').and.callFake((function(_this) {
-        return function(button, url, emoji, cb) {
-          return cb();
-        };
-      })(this));
+      loadAwardsHandler(true).then((obj) => {
+        awardsHandler = obj;
+        spyOn(awardsHandler, 'postEmoji').and.callFake((button, url, emoji, cb) => cb());
+        done();
+      }).catch(fail);
 
       let isEmojiMenuBuilt = false;
       openAndWaitForEmojiMenu = function() {
diff --git a/spec/javascripts/issue_show/components/app_spec.js b/spec/javascripts/issue_show/components/app_spec.js
index 9df923188647cf38f2e950109e6991b4adef161e..bc13373a27eb68f2340b96be0b37387ccaa78baf 100644
--- a/spec/javascripts/issue_show/components/app_spec.js
+++ b/spec/javascripts/issue_show/components/app_spec.js
@@ -42,9 +42,6 @@ describe('Issuable output', () => {
     }).$mount();
   });
 
-  afterEach(() => {
-  });
-
   it('should render a title/description/edited and update title/description/edited on update', (done) => {
     vm.poll.options.successCallback({
       json() {
diff --git a/spec/javascripts/notes_spec.js b/spec/javascripts/notes_spec.js
index 5ece4ed080b3d12cce8f0f1460dccf8014edc508..2c096ed08a8b09eac217699271a8a741936ad650 100644
--- a/spec/javascripts/notes_spec.js
+++ b/spec/javascripts/notes_spec.js
@@ -523,6 +523,51 @@ import '~/notes';
       });
     });
 
+    describe('postComment with Slash commands', () => {
+      const sampleComment = '/assign @root\n/award :100:';
+      const note = {
+        commands_changes: {
+          assignee_id: 1,
+          emoji_award: '100'
+        },
+        errors: {
+          commands_only: ['Commands applied']
+        },
+        valid: false
+      };
+      let $form;
+      let $notesContainer;
+
+      beforeEach(() => {
+        this.notes = new Notes('', []);
+        window.gon.current_username = 'root';
+        window.gon.current_user_fullname = 'Administrator';
+        gl.awardsHandler = {
+          addAwardToEmojiBar: () => {},
+          scrollToAwards: () => {}
+        };
+        gl.GfmAutoComplete = {
+          dataSources: {
+            commands: '/root/test-project/autocomplete_sources/commands'
+          }
+        };
+        $form = $('form.js-main-target-form');
+        $notesContainer = $('ul.main-notes-list');
+        $form.find('textarea.js-note-text').val(sampleComment);
+      });
+
+      it('should remove slash command placeholder when comment with slash commands is done posting', () => {
+        const deferred = $.Deferred();
+        spyOn($, 'ajax').and.returnValue(deferred.promise());
+        spyOn(gl.awardsHandler, 'addAwardToEmojiBar').and.callThrough();
+        $('.js-comment-button').click();
+
+        expect($notesContainer.find('.system-note.being-posted').length).toEqual(1); // Placeholder shown
+        deferred.resolve(note);
+        expect($notesContainer.find('.system-note.being-posted').length).toEqual(0); // Placeholder removed
+      });
+    });
+
     describe('update comment with script tags', () => {
       const sampleComment = '<script></script>';
       const updatedComment = '<script></script>';
diff --git a/spec/lib/banzai/filter/external_issue_reference_filter_spec.rb b/spec/lib/banzai/filter/external_issue_reference_filter_spec.rb
index a4bb043f8f182e1f075f6a3608575223c9d2f893..b7d82c36ddd5913c1be93c2369a4e6a64c3110f6 100644
--- a/spec/lib/banzai/filter/external_issue_reference_filter_spec.rb
+++ b/spec/lib/banzai/filter/external_issue_reference_filter_spec.rb
@@ -88,12 +88,12 @@ describe Banzai::Filter::ExternalIssueReferenceFilter, lib: true do
 
       it 'queries the collection on the first call' do
         expect_any_instance_of(Project).to receive(:default_issues_tracker?).once.and_call_original
-        expect_any_instance_of(Project).to receive(:issue_reference_pattern).once.and_call_original
+        expect_any_instance_of(Project).to receive(:external_issue_reference_pattern).once.and_call_original
 
         not_cached = reference_filter.call("look for #{reference}", { project: project })
 
         expect_any_instance_of(Project).not_to receive(:default_issues_tracker?)
-        expect_any_instance_of(Project).not_to receive(:issue_reference_pattern)
+        expect_any_instance_of(Project).not_to receive(:external_issue_reference_pattern)
 
         cached = reference_filter.call("look for #{reference}", { project: project })
 
diff --git a/spec/lib/banzai/filter/issue_reference_filter_spec.rb b/spec/lib/banzai/filter/issue_reference_filter_spec.rb
index e5c1deb338bcc09d2edc7d9e0f48f506c3eab275..a79d365d6c5ebfd517fd9f4b8b01199d65c0b970 100644
--- a/spec/lib/banzai/filter/issue_reference_filter_spec.rb
+++ b/spec/lib/banzai/filter/issue_reference_filter_spec.rb
@@ -39,13 +39,6 @@ describe Banzai::Filter::IssueReferenceFilter, lib: true do
 
     let(:reference) { "##{issue.iid}" }
 
-    it 'ignores valid references when using non-default tracker' do
-      allow(project).to receive(:default_issues_tracker?).and_return(false)
-
-      exp = act = "Issue #{reference}"
-      expect(reference_filter(act).to_html).to eq exp
-    end
-
     it 'links to a valid reference' do
       doc = reference_filter("Fixed #{reference}")
 
@@ -340,24 +333,6 @@ describe Banzai::Filter::IssueReferenceFilter, lib: true do
           .to eq({ project => { issue.iid => issue } })
       end
     end
-
-    context 'using an external issue tracker' do
-      it 'returns a Hash containing the issues per project' do
-        doc = Nokogiri::HTML.fragment('')
-        filter = described_class.new(doc, project: project)
-
-        expect(project).to receive(:default_issues_tracker?).and_return(false)
-
-        expect(filter).to receive(:projects_per_reference)
-          .and_return({ project.path_with_namespace => project })
-
-        expect(filter).to receive(:references_per_project)
-          .and_return({ project.path_with_namespace => Set.new([1]) })
-
-        expect(filter.issues_per_project[project][1])
-          .to be_an_instance_of(ExternalIssue)
-      end
-    end
   end
 
   describe '.references_in' do
diff --git a/spec/lib/banzai/pipeline/gfm_pipeline_spec.rb b/spec/lib/banzai/pipeline/gfm_pipeline_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..2b8c76f2bb8f49890fa85e0359e4e12e36f98f76
--- /dev/null
+++ b/spec/lib/banzai/pipeline/gfm_pipeline_spec.rb
@@ -0,0 +1,33 @@
+require 'rails_helper'
+
+describe Banzai::Pipeline::GfmPipeline do
+  describe 'integration between parsing regular and external issue references' do
+    let(:project) { create(:redmine_project, :public) }
+
+    it 'allows to use shorthand external reference syntax for Redmine' do
+      markdown = '#12'
+
+      result = described_class.call(markdown, project: project)[:output]
+      link = result.css('a').first
+
+      expect(link['href']).to eq 'http://redmine/projects/project_name_in_redmine/issues/12'
+    end
+
+    it 'parses cross-project references to regular issues' do
+      other_project = create(:empty_project, :public)
+      issue = create(:issue, project: other_project)
+      markdown = issue.to_reference(project, full: true)
+
+      result = described_class.call(markdown, project: project)[:output]
+      link = result.css('a').first
+
+      expect(link['href']).to eq(
+        Gitlab::Routing.url_helpers.namespace_project_issue_path(
+          other_project.namespace,
+          other_project,
+          issue
+        )
+      )
+    end
+  end
+end
diff --git a/spec/lib/banzai/reference_parser/issue_parser_spec.rb b/spec/lib/banzai/reference_parser/issue_parser_spec.rb
index 58e1a0c1bc1c399e973f47473fbd1e030fbccc27..acdd23f81f34bb1c9031699c846018751ddabe14 100644
--- a/spec/lib/banzai/reference_parser/issue_parser_spec.rb
+++ b/spec/lib/banzai/reference_parser/issue_parser_spec.rb
@@ -39,16 +39,6 @@ describe Banzai::ReferenceParser::IssueParser, lib: true do
         expect(subject.nodes_visible_to_user(user, [link])).to eq([])
       end
     end
-
-    context 'when the project uses an external issue tracker' do
-      it 'returns all nodes' do
-        link = double(:link)
-
-        expect(project).to receive(:external_issue_tracker).and_return(true)
-
-        expect(subject.nodes_visible_to_user(user, [link])).to eq([link])
-      end
-    end
   end
 
   describe '#referenced_by' do
diff --git a/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb b/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb
index af0e7855a9b2838997a5afbae16aad249a2bca86..482f03aa0cc05b5cd25a5ea8ce10c4529b27d112 100644
--- a/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb
+++ b/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb
@@ -598,8 +598,10 @@ module Ci
     describe "Image and service handling" do
       context "when extended docker configuration is used" do
         it "returns image and service when defined" do
-          config = YAML.dump({ image: { name: "ruby:2.1" },
-                               services: ["mysql", { name: "docker:dind", alias: "docker" }],
+          config = YAML.dump({ image: { name: "ruby:2.1", entrypoint: ["/usr/local/bin/init", "run"] },
+                               services: ["mysql", { name: "docker:dind", alias: "docker",
+                                                     entrypoint: ["/usr/local/bin/init", "run"],
+                                                     command: ["/usr/local/bin/init", "run"] }],
                                before_script: ["pwd"],
                                rspec: { script: "rspec" } })
 
@@ -614,8 +616,10 @@ module Ci
             coverage_regex: nil,
             tag_list: [],
             options: {
-                image: { name: "ruby:2.1" },
-                services: [{ name: "mysql" }, { name: "docker:dind", alias: "docker" }]
+                image: { name: "ruby:2.1", entrypoint: ["/usr/local/bin/init", "run"] },
+                services: [{ name: "mysql" },
+                           { name: "docker:dind", alias: "docker", entrypoint: ["/usr/local/bin/init", "run"],
+                             command: ["/usr/local/bin/init", "run"] }]
             },
             allow_failure: false,
             when: "on_success",
@@ -628,8 +632,11 @@ module Ci
           config = YAML.dump({ image: "ruby:2.1",
                                services: ["mysql"],
                                before_script: ["pwd"],
-                               rspec: { image: { name: "ruby:2.5" },
-                                        services: [{ name: "postgresql", alias: "db-pg" }, "docker:dind"], script: "rspec" } })
+                               rspec: { image: { name: "ruby:2.5", entrypoint: ["/usr/local/bin/init", "run"] },
+                                        services: [{ name: "postgresql", alias: "db-pg",
+                                                     entrypoint: ["/usr/local/bin/init", "run"],
+                                                     command: ["/usr/local/bin/init", "run"] }, "docker:dind"],
+                                        script: "rspec" } })
 
           config_processor = GitlabCiYamlProcessor.new(config, path)
 
@@ -642,8 +649,10 @@ module Ci
             coverage_regex: nil,
             tag_list: [],
             options: {
-                image: { name: "ruby:2.5" },
-                services: [{ name: "postgresql", alias: "db-pg" }, { name: "docker:dind" }]
+                image: { name: "ruby:2.5", entrypoint: ["/usr/local/bin/init", "run"] },
+                services: [{ name: "postgresql", alias: "db-pg", entrypoint: ["/usr/local/bin/init", "run"],
+                             command: ["/usr/local/bin/init", "run"] },
+                           { name: "docker:dind" }]
             },
             allow_failure: false,
             when: "on_success",
diff --git a/spec/lib/gitlab/ci/config/entry/image_spec.rb b/spec/lib/gitlab/ci/config/entry/image_spec.rb
index bca22e39500f0f61eef77530590b45e11e5f52b7..1a4d9ed5517167a4af06933c4d05944d8ed0d7d3 100644
--- a/spec/lib/gitlab/ci/config/entry/image_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/image_spec.rb
@@ -38,7 +38,7 @@ describe Gitlab::Ci::Config::Entry::Image do
   end
 
   context 'when configuration is a hash' do
-    let(:config) { { name: 'ruby:2.2', entrypoint: '/bin/sh' } }
+    let(:config) { { name: 'ruby:2.2', entrypoint: %w(/bin/sh run) } }
 
     describe '#value' do
       it 'returns image hash' do
@@ -66,7 +66,7 @@ describe Gitlab::Ci::Config::Entry::Image do
 
     describe '#entrypoint' do
       it "returns image's entrypoint" do
-        expect(entry.entrypoint).to eq '/bin/sh'
+        expect(entry.entrypoint).to eq %w(/bin/sh run)
       end
     end
   end
diff --git a/spec/lib/gitlab/ci/config/entry/service_spec.rb b/spec/lib/gitlab/ci/config/entry/service_spec.rb
index 7202fe525e426767613a29f063e85144d2badad8..9ebf947a751a1ba260bb0af742c79e03db25515b 100644
--- a/spec/lib/gitlab/ci/config/entry/service_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/service_spec.rb
@@ -43,7 +43,7 @@ describe Gitlab::Ci::Config::Entry::Service do
 
   context 'when configuration is a hash' do
     let(:config) do
-      { name: 'postgresql:9.5', alias: 'db', command: 'cmd', entrypoint: '/bin/sh' }
+      { name: 'postgresql:9.5', alias: 'db', command: %w(cmd run), entrypoint: %w(/bin/sh run) }
     end
 
     describe '#valid?' do
@@ -72,13 +72,13 @@ describe Gitlab::Ci::Config::Entry::Service do
 
     describe '#command' do
       it "returns service's command" do
-        expect(entry.command).to eq 'cmd'
+        expect(entry.command).to eq %w(cmd run)
       end
     end
 
     describe '#entrypoint' do
       it "returns service's entrypoint" do
-        expect(entry.entrypoint).to eq '/bin/sh'
+        expect(entry.entrypoint).to eq %w(/bin/sh run)
       end
     end
   end
diff --git a/spec/lib/gitlab/database/sha_attribute_spec.rb b/spec/lib/gitlab/database/sha_attribute_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..62c1d37ea1c8d422e2065343367e4f330d99147a
--- /dev/null
+++ b/spec/lib/gitlab/database/sha_attribute_spec.rb
@@ -0,0 +1,33 @@
+require 'spec_helper'
+
+describe Gitlab::Database::ShaAttribute do
+  let(:sha) do
+    '9a573a369a5bfbb9a4a36e98852c21af8a44ea8b'
+  end
+
+  let(:binary_sha) do
+    [sha].pack('H*')
+  end
+
+  let(:binary_from_db) do
+    if Gitlab::Database.postgresql?
+      "\\x#{sha}"
+    else
+      binary_sha
+    end
+  end
+
+  let(:attribute) { described_class.new }
+
+  describe '#type_cast_from_database' do
+    it 'converts the binary SHA to a String' do
+      expect(attribute.type_cast_from_database(binary_from_db)).to eq(sha)
+    end
+  end
+
+  describe '#type_cast_for_database' do
+    it 'converts a SHA String to binary data' do
+      expect(attribute.type_cast_for_database(sha).to_s).to eq(binary_sha)
+    end
+  end
+end
diff --git a/spec/lib/gitlab/git/hook_spec.rb b/spec/lib/gitlab/git/hook_spec.rb
index 3f279c2186546a1b08c19b5c25457d1d19caf870..73518656bde259bac6ca95ed230941f94c16f35e 100644
--- a/spec/lib/gitlab/git/hook_spec.rb
+++ b/spec/lib/gitlab/git/hook_spec.rb
@@ -4,18 +4,20 @@ require 'fileutils'
 describe Gitlab::Git::Hook, lib: true do
   describe "#trigger" do
     let(:project) { create(:project, :repository) }
+    let(:repo_path) { project.repository.path }
     let(:user) { create(:user) }
+    let(:gl_id) { Gitlab::GlId.gl_id(user) }
 
     def create_hook(name)
-      FileUtils.mkdir_p(File.join(project.repository.path, 'hooks'))
-      File.open(File.join(project.repository.path, 'hooks', name), 'w', 0755) do |f|
+      FileUtils.mkdir_p(File.join(repo_path, 'hooks'))
+      File.open(File.join(repo_path, 'hooks', name), 'w', 0755) do |f|
         f.write('exit 0')
       end
     end
 
     def create_failing_hook(name)
-      FileUtils.mkdir_p(File.join(project.repository.path, 'hooks'))
-      File.open(File.join(project.repository.path, 'hooks', name), 'w', 0755) do |f|
+      FileUtils.mkdir_p(File.join(repo_path, 'hooks'))
+      File.open(File.join(repo_path, 'hooks', name), 'w', 0755) do |f|
         f.write(<<-HOOK)
           echo 'regular message from the hook'
           echo 'error message from the hook' 1>&2
@@ -27,13 +29,29 @@ describe Gitlab::Git::Hook, lib: true do
     ['pre-receive', 'post-receive', 'update'].each do |hook_name|
       context "when triggering a #{hook_name} hook" do
         context "when the hook is successful" do
+          let(:hook_path) { File.join(repo_path, 'hooks', hook_name) }
+          let(:gl_repository) { Gitlab::GlRepository.gl_repository(project, false) }
+          let(:env) do
+            {
+              'GL_ID' => gl_id,
+              'PWD' => repo_path,
+              'GL_PROTOCOL' => 'web',
+              'GL_REPOSITORY' => gl_repository
+            }
+          end
+
           it "returns success with no errors" do
             create_hook(hook_name)
-            hook = Gitlab::Git::Hook.new(hook_name, project.repository.path)
+            hook = Gitlab::Git::Hook.new(hook_name, project)
             blank = Gitlab::Git::BLANK_SHA
             ref = Gitlab::Git::BRANCH_REF_PREFIX + 'new_branch'
 
-            status, errors = hook.trigger(Gitlab::GlId.gl_id(user), blank, blank, ref)
+            if hook_name != 'update'
+              expect(Open3).to receive(:popen3)
+                .with(env, hook_path, chdir: repo_path).and_call_original
+            end
+
+            status, errors = hook.trigger(gl_id, blank, blank, ref)
             expect(status).to be true
             expect(errors).to be_blank
           end
@@ -42,11 +60,11 @@ describe Gitlab::Git::Hook, lib: true do
         context "when the hook is unsuccessful" do
           it "returns failure with errors" do
             create_failing_hook(hook_name)
-            hook = Gitlab::Git::Hook.new(hook_name, project.repository.path)
+            hook = Gitlab::Git::Hook.new(hook_name, project)
             blank = Gitlab::Git::BLANK_SHA
             ref = Gitlab::Git::BRANCH_REF_PREFIX + 'new_branch'
 
-            status, errors = hook.trigger(Gitlab::GlId.gl_id(user), blank, blank, ref)
+            status, errors = hook.trigger(gl_id, blank, blank, ref)
             expect(status).to be false
             expect(errors).to eq("error message from the hook\n")
           end
@@ -56,11 +74,11 @@ describe Gitlab::Git::Hook, lib: true do
 
     context "when the hook doesn't exist" do
       it "returns success with no errors" do
-        hook = Gitlab::Git::Hook.new('unknown_hook', project.repository.path)
+        hook = Gitlab::Git::Hook.new('unknown_hook', project)
         blank = Gitlab::Git::BLANK_SHA
         ref = Gitlab::Git::BRANCH_REF_PREFIX + 'new_branch'
 
-        status, errors = hook.trigger(Gitlab::GlId.gl_id(user), blank, blank, ref)
+        status, errors = hook.trigger(gl_id, blank, blank, ref)
         expect(status).to be true
         expect(errors).to be_nil
       end
diff --git a/spec/lib/gitlab/import_export/repo_restorer_spec.rb b/spec/lib/gitlab/import_export/repo_restorer_spec.rb
index 168a59e51391ae7e18e55989f121f87ff5cc61ca..30b6a0d88458c806069a30b0979f69ce09c56e41 100644
--- a/spec/lib/gitlab/import_export/repo_restorer_spec.rb
+++ b/spec/lib/gitlab/import_export/repo_restorer_spec.rb
@@ -34,7 +34,7 @@ describe Gitlab::ImportExport::RepoRestorer, services: true do
     it 'has the webhooks' do
       restorer.restore
 
-      expect(Gitlab::Git::Hook.new('post-receive', project.repository.path_to_repo)).to exist
+      expect(Gitlab::Git::Hook.new('post-receive', project)).to exist
     end
   end
 end
diff --git a/spec/models/concerns/feature_gate_spec.rb b/spec/models/concerns/feature_gate_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..3f601243245588ff49427427f287524b7ef46028
--- /dev/null
+++ b/spec/models/concerns/feature_gate_spec.rb
@@ -0,0 +1,19 @@
+require 'spec_helper'
+
+describe FeatureGate do
+  describe 'User' do
+    describe '#flipper_id' do
+      context 'when user is not persisted' do
+        let(:user) { build(:user) }
+
+        it { expect(user.flipper_id).to be_nil }
+      end
+
+      context 'when user is persisted' do
+        let(:user) { create(:user) }
+
+        it { expect(user.flipper_id).to eq "User:#{user.id}" }
+      end
+    end
+  end
+end
diff --git a/spec/models/concerns/routable_spec.rb b/spec/models/concerns/routable_spec.rb
index 65f05121b403bf538f465a57c3e692f1f212e160..36aedd2f70142c156611f8c1b0c2297876f52bd2 100644
--- a/spec/models/concerns/routable_spec.rb
+++ b/spec/models/concerns/routable_spec.rb
@@ -132,6 +132,19 @@ describe Group, 'Routable' do
     end
   end
 
+  describe '#expires_full_path_cache' do
+    context 'with RequestStore active', :request_store do
+      it 'expires the full_path cache' do
+        expect(group.full_path).to eq('foo')
+
+        group.route.update(path: 'bar', name: 'bar')
+        group.expires_full_path_cache
+
+        expect(group.full_path).to eq('bar')
+      end
+    end
+  end
+
   describe '#full_name' do
     let(:group) { create(:group) }
     let(:nested_group) { create(:group, parent: group) }
diff --git a/spec/models/concerns/sha_attribute_spec.rb b/spec/models/concerns/sha_attribute_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..9e37c2b20c4aa50d850728c3dd626f67a38c785c
--- /dev/null
+++ b/spec/models/concerns/sha_attribute_spec.rb
@@ -0,0 +1,27 @@
+require 'spec_helper'
+
+describe ShaAttribute do
+  let(:model) { Class.new { include ShaAttribute } }
+
+  before do
+    columns = [
+      double(:column, name: 'name', type: :text),
+      double(:column, name: 'sha1', type: :binary)
+    ]
+
+    allow(model).to receive(:columns).and_return(columns)
+  end
+
+  describe '#sha_attribute' do
+    it 'defines a SHA attribute for a binary column' do
+      expect(model).to receive(:attribute)
+        .with(:sha1, an_instance_of(Gitlab::Database::ShaAttribute))
+
+      model.sha_attribute(:sha1)
+    end
+
+    it 'raises ArgumentError when the column type is not :binary' do
+      expect { model.sha_attribute(:name) }.to raise_error(ArgumentError)
+    end
+  end
+end
diff --git a/spec/models/concerns/sortable_spec.rb b/spec/models/concerns/sortable_spec.rb
deleted file mode 100644
index d1e17c4f6845342c6102b75e9bca14566da4131e..0000000000000000000000000000000000000000
--- a/spec/models/concerns/sortable_spec.rb
+++ /dev/null
@@ -1,21 +0,0 @@
-require 'spec_helper'
-
-describe Sortable do
-  let(:relation) { Issue.all }
-
-  describe '#where' do
-    it 'orders by id, descending' do
-      order_node = relation.where(iid: 1).order_values.first
-      expect(order_node).to be_a(Arel::Nodes::Descending)
-      expect(order_node.expr.name).to eq(:id)
-    end
-  end
-
-  describe '#find_by' do
-    it 'does not order' do
-      expect(relation).to receive(:unscope).with(:order).and_call_original
-
-      relation.find_by(iid: 1)
-    end
-  end
-end
diff --git a/spec/models/namespace_spec.rb b/spec/models/namespace_spec.rb
index d4f898f6d9fa1e91d9cbd0c122d8f69ba8b20ccf..62c4ea01ce175e287e0dedbe4d0df1c889b7014b 100644
--- a/spec/models/namespace_spec.rb
+++ b/spec/models/namespace_spec.rb
@@ -342,6 +342,17 @@ describe Namespace, models: true do
     end
   end
 
+  describe '#soft_delete_without_removing_associations' do
+    let(:project1) { create(:project_empty_repo, namespace: namespace) }
+
+    it 'updates the deleted_at timestamp but preserves projects' do
+      namespace.soft_delete_without_removing_associations
+
+      expect(Project.all).to include(project1)
+      expect(namespace.deleted_at).not_to be_nil
+    end
+  end
+
   describe '#user_ids_for_project_authorizations' do
     it 'returns the user IDs for which to refresh authorizations' do
       expect(namespace.user_ids_for_project_authorizations)
diff --git a/spec/models/project_services/jira_service_spec.rb b/spec/models/project_services/jira_service_spec.rb
index c86f56c55ebe768ba33dd0d5cdacb91764c67e0f..4a1de76f0995c4aa4b97c5f48dd30cbfc26d7d6e 100644
--- a/spec/models/project_services/jira_service_spec.rb
+++ b/spec/models/project_services/jira_service_spec.rb
@@ -64,12 +64,12 @@ describe JiraService, models: true do
     end
   end
 
-  describe '#reference_pattern' do
+  describe '.reference_pattern' do
     it_behaves_like 'allows project key on reference pattern'
 
     it 'does not allow # on the code' do
-      expect(subject.reference_pattern.match('#123')).to be_nil
-      expect(subject.reference_pattern.match('1#23#12')).to be_nil
+      expect(described_class.reference_pattern.match('#123')).to be_nil
+      expect(described_class.reference_pattern.match('1#23#12')).to be_nil
     end
   end
 
diff --git a/spec/models/project_services/redmine_service_spec.rb b/spec/models/project_services/redmine_service_spec.rb
index 6631d9040b13fb674b2493622aa8f2d31b724a74..441b3f896cab206527917c17308c9ed1c343992a 100644
--- a/spec/models/project_services/redmine_service_spec.rb
+++ b/spec/models/project_services/redmine_service_spec.rb
@@ -31,11 +31,11 @@ describe RedmineService, models: true do
     end
   end
 
-  describe '#reference_pattern' do
+  describe '.reference_pattern' do
     it_behaves_like 'allows project key on reference pattern'
 
     it 'does allow # on the reference' do
-      expect(subject.reference_pattern.match('#123')[:issue]).to eq('123')
+      expect(described_class.reference_pattern.match('#123')[:issue]).to eq('123')
     end
   end
 end
diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb
index 5565fd2d391b33b11e87439302055a7a36d02e77..6ff4ec3d417dbe1d2e3447b54f9af7c42890d1fa 100644
--- a/spec/models/project_spec.rb
+++ b/spec/models/project_spec.rb
@@ -1170,6 +1170,16 @@ describe Project, models: true do
 
       expect(relation.search(project.namespace.name)).to eq([project])
     end
+
+    describe 'with pending_delete project' do
+      let(:pending_delete_project) { create(:empty_project, pending_delete: true) }
+
+      it 'shows pending deletion project' do
+        search_result = described_class.search(pending_delete_project.name)
+
+        expect(search_result).to eq([pending_delete_project])
+      end
+    end
   end
 
   describe '#rename_repo' do
@@ -1206,6 +1216,8 @@ describe Project, models: true do
 
       expect(project).to receive(:expire_caches_before_rename)
 
+      expect(project).to receive(:expires_full_path_cache)
+
       project.rename_repo
     end
 
diff --git a/spec/models/repository_spec.rb b/spec/models/repository_spec.rb
index 3e984ec7588a68fdd4ec7571c537f69365fdbe6c..c69f0a495db0b8d198b2481f4f154e10b15006f6 100644
--- a/spec/models/repository_spec.rb
+++ b/spec/models/repository_spec.rb
@@ -780,7 +780,7 @@ describe Repository, models: true do
     context 'when pre hooks were successful' do
       it 'runs without errors' do
         expect_any_instance_of(GitHooksService).to receive(:execute)
-          .with(user, project.repository.path_to_repo, old_rev, blank_sha, 'refs/heads/feature')
+          .with(user, project, old_rev, blank_sha, 'refs/heads/feature')
 
         expect { repository.rm_branch(user, 'feature') }.not_to raise_error
       end
@@ -823,12 +823,7 @@ describe Repository, models: true do
         service = GitHooksService.new
         expect(GitHooksService).to receive(:new).and_return(service)
         expect(service).to receive(:execute)
-          .with(
-            user,
-            repository.path_to_repo,
-            old_rev,
-            new_rev,
-            'refs/heads/feature')
+          .with(user, project, old_rev, new_rev, 'refs/heads/feature')
           .and_yield(service).and_return(true)
       end
 
@@ -1474,9 +1469,9 @@ describe Repository, models: true do
 
       it 'passes commit SHA to pre-receive and update hooks,\
         and tag SHA to post-receive hook' do
-        pre_receive_hook = Gitlab::Git::Hook.new('pre-receive', repository.path_to_repo)
-        update_hook = Gitlab::Git::Hook.new('update', repository.path_to_repo)
-        post_receive_hook = Gitlab::Git::Hook.new('post-receive', repository.path_to_repo)
+        pre_receive_hook = Gitlab::Git::Hook.new('pre-receive', project)
+        update_hook = Gitlab::Git::Hook.new('update', project)
+        post_receive_hook = Gitlab::Git::Hook.new('post-receive', project)
 
         allow(Gitlab::Git::Hook).to receive(:new)
           .and_return(pre_receive_hook, update_hook, post_receive_hook)
diff --git a/spec/requests/api/features_spec.rb b/spec/requests/api/features_spec.rb
index f169e6661d19cd156f1ad390f5c13dcf6c77fd81..1d8aaeea8f2279f58a9b2ba4025207925609cce1 100644
--- a/spec/requests/api/features_spec.rb
+++ b/spec/requests/api/features_spec.rb
@@ -4,6 +4,13 @@ describe API::Features do
   let(:user)  { create(:user) }
   let(:admin) { create(:admin) }
 
+  before do
+    Flipper.unregister_groups
+    Flipper.register(:perf_team) do |actor|
+      actor.respond_to?(:admin) && actor.admin?
+    end
+  end
+
   describe 'GET /features' do
     let(:expected_features) do
       [
@@ -16,6 +23,14 @@ describe API::Features do
           'name' => 'feature_2',
           'state' => 'off',
           'gates' => [{ 'key' => 'boolean', 'value' => false }]
+        },
+        {
+          'name' => 'feature_3',
+          'state' => 'conditional',
+          'gates' => [
+            { 'key' => 'boolean', 'value' => false },
+            { 'key' => 'groups', 'value' => ['perf_team'] }
+          ]
         }
       ]
     end
@@ -23,6 +38,7 @@ describe API::Features do
     before do
       Feature.get('feature_1').enable
       Feature.get('feature_2').disable
+      Feature.get('feature_3').enable Feature.group(:perf_team)
     end
 
     it 'returns a 401 for anonymous users' do
@@ -47,30 +63,70 @@ describe API::Features do
 
   describe 'POST /feature' do
     let(:feature_name) { 'my_feature' }
-    it 'returns a 401 for anonymous users' do
-      post api("/features/#{feature_name}")
 
-      expect(response).to have_http_status(401)
-    end
+    context 'when the feature does not exist' do
+      it 'returns a 401 for anonymous users' do
+        post api("/features/#{feature_name}")
 
-    it 'returns a 403 for users' do
-      post api("/features/#{feature_name}", user)
+        expect(response).to have_http_status(401)
+      end
 
-      expect(response).to have_http_status(403)
-    end
+      it 'returns a 403 for users' do
+        post api("/features/#{feature_name}", user)
 
-    it 'creates an enabled feature if passed true' do
-      post api("/features/#{feature_name}", admin), value: 'true'
+        expect(response).to have_http_status(403)
+      end
 
-      expect(response).to have_http_status(201)
-      expect(Feature.get(feature_name)).to be_enabled
-    end
+      context 'when passed value=true' do
+        it 'creates an enabled feature' do
+          post api("/features/#{feature_name}", admin), value: 'true'
 
-    it 'creates a feature with the given percentage if passed an integer' do
-      post api("/features/#{feature_name}", admin), value: '50'
+          expect(response).to have_http_status(201)
+          expect(json_response).to eq(
+            'name' => 'my_feature',
+            'state' => 'on',
+            'gates' => [{ 'key' => 'boolean', 'value' => true }])
+        end
+
+        it 'creates an enabled feature for the given Flipper group when passed feature_group=perf_team' do
+          post api("/features/#{feature_name}", admin), value: 'true', feature_group: 'perf_team'
+
+          expect(response).to have_http_status(201)
+          expect(json_response).to eq(
+            'name' => 'my_feature',
+            'state' => 'conditional',
+            'gates' => [
+              { 'key' => 'boolean', 'value' => false },
+              { 'key' => 'groups', 'value' => ['perf_team'] }
+            ])
+        end
+
+        it 'creates an enabled feature for the given user when passed user=username' do
+          post api("/features/#{feature_name}", admin), value: 'true', user: user.username
 
-      expect(response).to have_http_status(201)
-      expect(Feature.get(feature_name).percentage_of_time_value).to be(50)
+          expect(response).to have_http_status(201)
+          expect(json_response).to eq(
+            'name' => 'my_feature',
+            'state' => 'conditional',
+            'gates' => [
+              { 'key' => 'boolean', 'value' => false },
+              { 'key' => 'actors', 'value' => ["User:#{user.id}"] }
+            ])
+        end
+      end
+
+      it 'creates a feature with the given percentage if passed an integer' do
+        post api("/features/#{feature_name}", admin), value: '50'
+
+        expect(response).to have_http_status(201)
+        expect(json_response).to eq(
+          'name' => 'my_feature',
+          'state' => 'conditional',
+          'gates' => [
+            { 'key' => 'boolean', 'value' => false },
+            { 'key' => 'percentage_of_time', 'value' => 50 }
+          ])
+      end
     end
 
     context 'when the feature exists' do
@@ -80,11 +136,83 @@ describe API::Features do
         feature.disable # This also persists the feature on the DB
       end
 
-      it 'enables the feature if passed true' do
-        post api("/features/#{feature_name}", admin), value: 'true'
+      context 'when passed value=true' do
+        it 'enables the feature' do
+          post api("/features/#{feature_name}", admin), value: 'true'
 
-        expect(response).to have_http_status(201)
-        expect(feature).to be_enabled
+          expect(response).to have_http_status(201)
+          expect(json_response).to eq(
+            'name' => 'my_feature',
+            'state' => 'on',
+            'gates' => [{ 'key' => 'boolean', 'value' => true }])
+        end
+
+        it 'enables the feature for the given Flipper group when passed feature_group=perf_team' do
+          post api("/features/#{feature_name}", admin), value: 'true', feature_group: 'perf_team'
+
+          expect(response).to have_http_status(201)
+          expect(json_response).to eq(
+            'name' => 'my_feature',
+            'state' => 'conditional',
+            'gates' => [
+              { 'key' => 'boolean', 'value' => false },
+              { 'key' => 'groups', 'value' => ['perf_team'] }
+            ])
+        end
+
+        it 'enables the feature for the given user when passed user=username' do
+          post api("/features/#{feature_name}", admin), value: 'true', user: user.username
+
+          expect(response).to have_http_status(201)
+          expect(json_response).to eq(
+            'name' => 'my_feature',
+            'state' => 'conditional',
+            'gates' => [
+              { 'key' => 'boolean', 'value' => false },
+              { 'key' => 'actors', 'value' => ["User:#{user.id}"] }
+            ])
+        end
+      end
+
+      context 'when feature is enabled and value=false is passed' do
+        it 'disables the feature' do
+          feature.enable
+          expect(feature).to be_enabled
+
+          post api("/features/#{feature_name}", admin), value: 'false'
+
+          expect(response).to have_http_status(201)
+          expect(json_response).to eq(
+            'name' => 'my_feature',
+            'state' => 'off',
+            'gates' => [{ 'key' => 'boolean', 'value' => false }])
+        end
+
+        it 'disables the feature for the given Flipper group when passed feature_group=perf_team' do
+          feature.enable(Feature.group(:perf_team))
+          expect(Feature.get(feature_name).enabled?(admin)).to be_truthy
+
+          post api("/features/#{feature_name}", admin), value: 'false', feature_group: 'perf_team'
+
+          expect(response).to have_http_status(201)
+          expect(json_response).to eq(
+            'name' => 'my_feature',
+            'state' => 'off',
+            'gates' => [{ 'key' => 'boolean', 'value' => false }])
+        end
+
+        it 'disables the feature for the given user when passed user=username' do
+          feature.enable(user)
+          expect(Feature.get(feature_name).enabled?(user)).to be_truthy
+
+          post api("/features/#{feature_name}", admin), value: 'false', user: user.username
+
+          expect(response).to have_http_status(201)
+          expect(json_response).to eq(
+            'name' => 'my_feature',
+            'state' => 'off',
+            'gates' => [{ 'key' => 'boolean', 'value' => false }])
+        end
       end
 
       context 'with a pre-existing percentage value' do
@@ -96,7 +224,13 @@ describe API::Features do
           post api("/features/#{feature_name}", admin), value: '30'
 
           expect(response).to have_http_status(201)
-          expect(Feature.get(feature_name).percentage_of_time_value).to be(30)
+          expect(json_response).to eq(
+            'name' => 'my_feature',
+            'state' => 'conditional',
+            'gates' => [
+              { 'key' => 'boolean', 'value' => false },
+              { 'key' => 'percentage_of_time', 'value' => 30 }
+            ])
         end
       end
     end
diff --git a/spec/services/git_hooks_service_spec.rb b/spec/services/git_hooks_service_spec.rb
index ac7ccfbaab01dfd482e7c6d3e6de5b209b6fd3bd..213678c27f5b73b6b8869da07ff33c6efec9864a 100644
--- a/spec/services/git_hooks_service_spec.rb
+++ b/spec/services/git_hooks_service_spec.rb
@@ -12,7 +12,6 @@ describe GitHooksService, services: true do
     @oldrev = sample_commit.parent_id
     @newrev = sample_commit.id
     @ref = 'refs/heads/feature'
-    @repo_path = project.repository.path_to_repo
   end
 
   describe '#execute' do
@@ -21,7 +20,7 @@ describe GitHooksService, services: true do
         hook = double(trigger: [true, nil])
         expect(Gitlab::Git::Hook).to receive(:new).exactly(3).times.and_return(hook)
 
-        service.execute(user, @repo_path, @blankrev, @newrev, @ref) { }
+        service.execute(user, project, @blankrev, @newrev, @ref) { }
       end
     end
 
@@ -31,7 +30,7 @@ describe GitHooksService, services: true do
         expect(service).not_to receive(:run_hook).with('post-receive')
 
         expect do
-          service.execute(user, @repo_path, @blankrev, @newrev, @ref)
+          service.execute(user, project, @blankrev, @newrev, @ref)
         end.to raise_error(GitHooksService::PreReceiveError)
       end
     end
@@ -43,7 +42,7 @@ describe GitHooksService, services: true do
         expect(service).not_to receive(:run_hook).with('post-receive')
 
         expect do
-          service.execute(user, @repo_path, @blankrev, @newrev, @ref)
+          service.execute(user, project, @blankrev, @newrev, @ref)
         end.to raise_error(GitHooksService::PreReceiveError)
       end
     end
diff --git a/spec/services/git_push_service_spec.rb b/spec/services/git_push_service_spec.rb
index ca827fc0f39de230bc814b870e925862b4c8dc02..8e8816870e1a30919233738eac30bfd5dbde6538 100644
--- a/spec/services/git_push_service_spec.rb
+++ b/spec/services/git_push_service_spec.rb
@@ -401,18 +401,6 @@ describe GitPushService, services: true do
         expect(SystemNoteService).not_to receive(:cross_reference)
         execute_service(project, commit_author, @oldrev, @newrev, @ref )
       end
-
-      it "doesn't close issues when external issue tracker is in use" do
-        allow_any_instance_of(Project).to receive(:default_issues_tracker?)
-          .and_return(false)
-        external_issue_tracker = double(title: 'My Tracker', issue_path: issue.iid, reference_pattern: project.issue_reference_pattern)
-        allow_any_instance_of(Project).to receive(:external_issue_tracker).and_return(external_issue_tracker)
-
-        # The push still shouldn't create cross-reference notes.
-        expect do
-          execute_service(project, commit_author, @oldrev, @newrev,  'refs/heads/hurf' )
-        end.not_to change { Note.where(project_id: project.id, system: true).count }
-      end
     end
 
     context "to non-default branches" do
diff --git a/spec/services/groups/destroy_service_spec.rb b/spec/services/groups/destroy_service_spec.rb
index a37257d1bf4846abe08f7eeb055ff31b353c3c31..d59b37bee360ceff8e40ca2c952136021d2d8823 100644
--- a/spec/services/groups/destroy_service_spec.rb
+++ b/spec/services/groups/destroy_service_spec.rb
@@ -15,6 +15,14 @@ describe Groups::DestroyService, services: true do
     group.add_user(user, Gitlab::Access::OWNER)
   end
 
+  def destroy_group(group, user, async)
+    if async
+      Groups::DestroyService.new(group, user).async_execute
+    else
+      Groups::DestroyService.new(group, user).execute
+    end
+  end
+
   shared_examples 'group destruction' do |async|
     context 'database records' do
       before do
@@ -30,30 +38,14 @@ describe Groups::DestroyService, services: true do
     context 'file system' do
       context 'Sidekiq inline' do
         before do
-          # Run sidekiq immediatly to check that renamed dir will be removed
+          # Run sidekiq immediately to check that renamed dir will be removed
           Sidekiq::Testing.inline! { destroy_group(group, user, async) }
         end
 
-        it { expect(gitlab_shell.exists?(project.repository_storage_path, group.path)).to be_falsey }
-        it { expect(gitlab_shell.exists?(project.repository_storage_path, remove_path)).to be_falsey }
-      end
-
-      context 'Sidekiq fake' do
-        before do
-          # Don't run sidekiq to check if renamed repository exists
-          Sidekiq::Testing.fake! { destroy_group(group, user, async) }
+        it 'verifies that paths have been deleted' do
+          expect(gitlab_shell.exists?(project.repository_storage_path, group.path)).to be_falsey
+          expect(gitlab_shell.exists?(project.repository_storage_path, remove_path)).to be_falsey
         end
-
-        it { expect(gitlab_shell.exists?(project.repository_storage_path, group.path)).to be_falsey }
-        it { expect(gitlab_shell.exists?(project.repository_storage_path, remove_path)).to be_truthy }
-      end
-    end
-
-    def destroy_group(group, user, async)
-      if async
-        Groups::DestroyService.new(group, user).async_execute
-      else
-        Groups::DestroyService.new(group, user).execute
       end
     end
   end
@@ -61,6 +53,26 @@ describe Groups::DestroyService, services: true do
   describe 'asynchronous delete' do
     it_behaves_like 'group destruction', true
 
+    context 'Sidekiq fake' do
+      before do
+        # Don't run Sidekiq to verify that group and projects are not actually destroyed
+        Sidekiq::Testing.fake! { destroy_group(group, user, true) }
+      end
+
+      after do
+        # Clean up stale directories
+        gitlab_shell.rm_namespace(project.repository_storage_path, group.path)
+        gitlab_shell.rm_namespace(project.repository_storage_path, remove_path)
+      end
+
+      it 'verifies original paths and projects still exist' do
+        expect(gitlab_shell.exists?(project.repository_storage_path, group.path)).to be_truthy
+        expect(gitlab_shell.exists?(project.repository_storage_path, remove_path)).to be_falsey
+        expect(Project.unscoped.count).to eq(1)
+        expect(Group.unscoped.count).to eq(2)
+      end
+    end
+
     context 'potential race conditions' do
       context "when the `GroupDestroyWorker` task runs immediately" do
         it "deletes the group" do
diff --git a/spec/services/projects/transfer_service_spec.rb b/spec/services/projects/transfer_service_spec.rb
index 76c52d55ae58f53d28f4f9b3cfc9ba91cbbde001..441a5276c5615601643ecb8baaab1028b56d5361 100644
--- a/spec/services/projects/transfer_service_spec.rb
+++ b/spec/services/projects/transfer_service_spec.rb
@@ -30,6 +30,12 @@ describe Projects::TransferService, services: true do
       transfer_project(project, user, group)
     end
 
+    it 'expires full_path cache' do
+      expect(project).to receive(:expires_full_path_cache)
+
+      transfer_project(project, user, group)
+    end
+
     it 'executes system hooks' do
       expect_any_instance_of(Projects::TransferService).to receive(:execute_system_hooks)
 
diff --git a/spec/support/issue_tracker_service_shared_example.rb b/spec/support/issue_tracker_service_shared_example.rb
index e70b3963d9d068dbf3598a71e054d19d24232aa3..a6ab03cb808f8c747972d19101eb587e7d93ea44 100644
--- a/spec/support/issue_tracker_service_shared_example.rb
+++ b/spec/support/issue_tracker_service_shared_example.rb
@@ -8,15 +8,15 @@ end
 
 RSpec.shared_examples 'allows project key on reference pattern' do |url_attr|
   it 'allows underscores in the project name' do
-    expect(subject.reference_pattern.match('EXT_EXT-1234')[0]).to eq 'EXT_EXT-1234'
+    expect(described_class.reference_pattern.match('EXT_EXT-1234')[0]).to eq 'EXT_EXT-1234'
   end
 
   it 'allows numbers in the project name' do
-    expect(subject.reference_pattern.match('EXT3_EXT-1234')[0]).to eq 'EXT3_EXT-1234'
+    expect(described_class.reference_pattern.match('EXT3_EXT-1234')[0]).to eq 'EXT3_EXT-1234'
   end
 
   it 'requires the project name to begin with A-Z' do
-    expect(subject.reference_pattern.match('3EXT_EXT-1234')).to eq nil
-    expect(subject.reference_pattern.match('EXT_EXT-1234')[0]).to eq 'EXT_EXT-1234'
+    expect(described_class.reference_pattern.match('3EXT_EXT-1234')).to eq nil
+    expect(described_class.reference_pattern.match('EXT_EXT-1234')[0]).to eq 'EXT_EXT-1234'
   end
 end
diff --git a/spec/support/shared_examples/features/issuable_sidebar_shared_examples.rb b/spec/support/shared_examples/features/issuable_sidebar_shared_examples.rb
new file mode 100644
index 0000000000000000000000000000000000000000..96c821b26f72deb94b82b48a7276fc5189de52dd
--- /dev/null
+++ b/spec/support/shared_examples/features/issuable_sidebar_shared_examples.rb
@@ -0,0 +1,9 @@
+shared_examples 'issue sidebar stays collapsed on mobile' do
+  before do
+    resize_screen_xs
+  end
+
+  it 'keeps the sidebar collapsed' do
+    expect(page).not_to have_css('.right-sidebar.right-sidebar-collapsed')
+  end
+end
diff --git a/yarn.lock b/yarn.lock
index b902d5235d012498dd51d3a9f6fa21d0e34ce207..b04eebe60af2420d4f287b89d8f35c78fe36e474 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -265,6 +265,15 @@ babel-core@^6.22.1, babel-core@^6.23.0:
     slash "^1.0.0"
     source-map "^0.5.0"
 
+babel-eslint@^7.2.1:
+  version "7.2.1"
+  resolved "https://registry.yarnpkg.com/babel-eslint/-/babel-eslint-7.2.1.tgz#079422eb73ba811e3ca0865ce87af29327f8c52f"
+  dependencies:
+    babel-code-frame "^6.22.0"
+    babel-traverse "^6.23.1"
+    babel-types "^6.23.0"
+    babylon "^6.16.1"
+
 babel-generator@^6.18.0, babel-generator@^6.23.0:
   version "6.23.0"
   resolved "https://registry.yarnpkg.com/babel-generator/-/babel-generator-6.23.0.tgz#6b8edab956ef3116f79d8c84c5a3c05f32a74bc5"
@@ -816,10 +825,14 @@ babel-types@^6.18.0, babel-types@^6.19.0, babel-types@^6.22.0, babel-types@^6.23
     lodash "^4.2.0"
     to-fast-properties "^1.0.1"
 
-babylon@^6.11.0, babylon@^6.13.0, babylon@^6.15.0:
+babylon@^6.11.0:
   version "6.15.0"
   resolved "https://registry.yarnpkg.com/babylon/-/babylon-6.15.0.tgz#ba65cfa1a80e1759b0e89fb562e27dccae70348e"
 
+babylon@^6.13.0, babylon@^6.15.0, babylon@^6.16.1:
+  version "6.16.1"
+  resolved "https://registry.yarnpkg.com/babylon/-/babylon-6.16.1.tgz#30c5a22f481978a9e7f8cdfdf496b11d94b404d3"
+
 backo2@1.0.2:
   version "1.0.2"
   resolved "https://registry.yarnpkg.com/backo2/-/backo2-1.0.2.tgz#31ab1ac8b129363463e35b3ebb69f4dfcfba7947"