diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index ace0de5cad3db69220c873bb934fdb9ef06febe5..76117a48730901331b3f87e324ce4dcff8e29224 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -22,7 +22,7 @@ before_script:
   - bundle --version
   - '[ "$USE_BUNDLE_INSTALL" != "true" ] || retry bundle install --without postgres production --jobs $(nproc) "${FLAGS[@]}"'
   - retry gem install knapsack
-  - '[ "$SETUP_DB" != "true" ] || bundle exec rake db:drop db:create db:schema:load db:migrate'
+  - '[ "$SETUP_DB" != "true" ] || bundle exec rake db:drop db:create db:schema:load db:migrate add_limits_mysql'
 
 stages:
 - prepare
diff --git a/CHANGELOG.md b/CHANGELOG.md
index dd88b348734005ab185808bded1971f2d45e9c81..494089490587875943ee78ca594a78fe145dcd3c 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -11,6 +11,7 @@ Please view this file on the master branch, on stable branches it's out of date.
   - Update runner version only when updating contacted_at
   - Add link from system note to compare with previous version
   - Use gitlab-shell v3.6.6
+  - Ability to resolve merge request conflicts with editor !6374
   - Add `/projects/visible` API endpoint (Ben Boeckel)
   - Fix centering of custom header logos (Ashley Dumaine)
   - ExpireBuildArtifactsWorker query builds table without ordering enqueuing one job per build to cleanup
@@ -19,9 +20,9 @@ Please view this file on the master branch, on stable branches it's out of date.
   - Clarify documentation for Runners API (Gennady Trafimenkov)
   - The instrumentation for Banzai::Renderer has been restored
   - Change user & group landing page routing from /u/:username to /:username
-  - Prevent running GfmAutocomplete setup for each diff note !6569
   - Added documentation for .gitattributes files
   - Add CI notifications. Who triggered a pipeline would receive an email after the pipeline is succeeded or failed. Users could also update notification settings accordingly. !6342
+  - Move Pipeline Metrics to separate worker
   - AbstractReferenceFilter caches project_refs on RequestStore when active
   - Replaced the check sign to arrow in the show build view. !6501
   - Add a /wip slash command to toggle the Work In Progress status of a merge request. !6259 (tbalthazar)
@@ -40,7 +41,6 @@ Please view this file on the master branch, on stable branches it's out of date.
   - Update Gitlab Shell to fix some problems with moving projects between storages
   - Cache rendered markdown in the database, rather than Redis
   - Avoid database queries on Banzai::ReferenceParser::BaseParser for nodes without references
-  - Do not alter 'force_remove_source_branch' options on MergeRequest unless specified
   - Simplify Mentionable concern instance methods
   - API: Ability to retrieve version information (Robert Schilling)
   - Fix permission for setting an issue's due date
@@ -57,6 +57,7 @@ Please view this file on the master branch, on stable branches it's out of date.
   - Added soft wrap button to repository file/blob editor
   - Update namespace validation to forbid reserved names (.git and .atom) (Will Starms)
   - Show the time ago a merge request was deployed to an environment
+  - Add RTL support to markdown renderer (Ebrahim Byagowi)
   - Add word-wrap to issue title on issue and milestone boards (ClemMakesApps)
   - Fix todos page mobile viewport layout (ClemMakesApps)
   - Fix inconsistent highlighting of already selected activity nav-links (ClemMakesApps)
@@ -76,14 +77,12 @@ Please view this file on the master branch, on stable branches it's out of date.
   - Only update issuable labels if they have been changed
   - Take filters in account in issuable counters. !6496
   - Use custom Ruby images to test builds (registry.dev.gitlab.org/gitlab/gitlab-build-images:*)
-  - Prevent flash alert text from being obscured when container is fluid
   - Append issue template to existing description !6149 (Joseph Frazier)
   - Trending projects now only show public projects and the list of projects is cached for a day
   - Memoize Gitlab Shell's secret token (!6599, Justin DiPierro)
   - Revoke button in Applications Settings underlines on hover.
   - Use higher size on Gitlab::Redis connection pool on Sidekiq servers
   - Add missing values to linter !6276 (Katarzyna Kobierska Ula Budziszewska)
-  - Fix Long commit messages overflow viewport in file tree
   - Revert avoid touching file system on Build#artifacts?
   - Stop using a Redis lease when updating the project activity timestamp whenever a new event is created
   - Add disabled delete button to protected branches (ClemMakesApps)
@@ -94,7 +93,7 @@ Please view this file on the master branch, on stable branches it's out of date.
   - Replace bootstrap caret with fontawesome caret (ClemMakesApps)
   - Fix unnecessary escaping of reserved HTML characters in milestone title. !6533
   - Add organization field to user profile
-  - Ignore deployment for statistics in Cycle Analytics, except in staging and production stages
+  - Change user pages routing from /u/:username/PATH to /users/:username/PATH. Old routes will redirect to the new ones for the time being.
   - Fix enter key when navigating search site search dropdown. !6643 (Brennan Roberts)
   - Fix deploy status responsiveness error !6633
   - Make searching for commits case insensitive
@@ -124,8 +123,15 @@ Please view this file on the master branch, on stable branches it's out of date.
 
 ## 8.12.7
 
-  - Use gitlab-markup gem instead of github-markup to fix `.rst` file rendering. !6659
-  - Fix GFM autocomplete setup being called several times
+  - Prevent running `GfmAutocomplete` setup for each diff note. !6569
+  - Fix long commit messages overflow viewport in file tree. !6573
+  - Use `gitlab-markup` gem instead of `github-markup` to fix `.rst` file rendering. !6659
+  - Prevent flash alert text from being obscured when container is fluid. !6694
+  - Fix due date being displayed as `NaN` in Safari. !6797
+  - Fix JS bug with select2 because of missing `data-field` attribute in select box. !6812
+  - Do not alter `force_remove_source_branch` options on MergeRequest unless specified. !6817
+  - Fix GFM autocomplete setup being called several times. !6840
+  - Handle case where deployment ref no longer exists. !6855
 
 ## 8.12.6
 
diff --git a/Gemfile b/Gemfile
index 5f754c1b66fe5d272781b42d27dfc3ce88f47ae5..05166b6a828b5ef2812512122ea141158bb6d993 100644
--- a/Gemfile
+++ b/Gemfile
@@ -262,8 +262,6 @@ group :development do
 
   # thin instead webrick
   gem 'thin', '~> 1.7.0'
-
-  gem 'activerecord_sane_schema_dumper', '0.2'
 end
 
 group :development, :test do
@@ -310,6 +308,8 @@ group :development, :test do
 
   gem 'license_finder', '~> 2.1.0', require: false
   gem 'knapsack', '~> 1.11.0'
+
+  gem 'activerecord_sane_schema_dumper', '0.2'
 end
 
 group :test do
diff --git a/app/assets/javascripts/dispatcher.js.es6 b/app/assets/javascripts/dispatcher.js.es6
index f3957ed374b54b020802180a55a558a9b0674898..73691f40c74e6d45f04ea39f125f1abed85c8593 100644
--- a/app/assets/javascripts/dispatcher.js.es6
+++ b/app/assets/javascripts/dispatcher.js.es6
@@ -50,7 +50,7 @@
         case 'projects:milestones:new':
         case 'projects:milestones:edit':
           new ZenMode();
-          new DueDateSelect();
+          new gl.DueDateSelectors();
           new GLForm($('.milestone-form'));
           break;
         case 'groups:milestones:new':
@@ -101,9 +101,6 @@
           new ZenMode();
           new MergedButtons();
           break;
-        case "projects:merge_requests:conflicts":
-          window.mcui = new MergeConflictResolver()
-          break;
         case 'projects:merge_requests:index':
           shortcut_handler = new ShortcutsNavigation();
           Issuable.init();
diff --git a/app/assets/javascripts/due_date_select.js b/app/assets/javascripts/due_date_select.js
deleted file mode 100644
index bf68b7e3a9b757a3d51293645b906c6d5960a1df..0000000000000000000000000000000000000000
--- a/app/assets/javascripts/due_date_select.js
+++ /dev/null
@@ -1,107 +0,0 @@
-(function() {
-  this.DueDateSelect = (function() {
-    function DueDateSelect() {
-      var $datePicker, $dueDate, $loading;
-      // Milestone edit/new form
-      $datePicker = $('.datepicker');
-      if ($datePicker.length) {
-        $dueDate = $('#milestone_due_date');
-        $datePicker.datepicker({
-          dateFormat: 'yy-mm-dd',
-          onSelect: function(dateText, inst) {
-            return $dueDate.val(dateText);
-          }
-        }).datepicker('setDate', $.datepicker.parseDate('yy-mm-dd', $dueDate.val()));
-      }
-      $('.js-clear-due-date').on('click', function(e) {
-        e.preventDefault();
-        return $.datepicker._clearDate($datePicker);
-      });
-      // Issuable sidebar
-      $loading = $('.js-issuable-update .due_date').find('.block-loading').hide();
-      $('.js-due-date-select').each(function(i, dropdown) {
-        var $block, $dropdown, $dropdownParent, $selectbox, $sidebarValue, $value, $valueContent, abilityName, addDueDate, fieldName, issueUpdateURL;
-        $dropdown = $(dropdown);
-        $dropdownParent = $dropdown.closest('.dropdown');
-        $datePicker = $dropdownParent.find('.js-due-date-calendar');
-        $block = $dropdown.closest('.block');
-        $selectbox = $dropdown.closest('.selectbox');
-        $value = $block.find('.value');
-        $valueContent = $block.find('.value-content');
-        $sidebarValue = $('.js-due-date-sidebar-value', $block);
-        fieldName = $dropdown.data('field-name');
-        abilityName = $dropdown.data('ability-name');
-        issueUpdateURL = $dropdown.data('issue-update');
-        $dropdown.glDropdown({
-          hidden: function() {
-            $selectbox.hide();
-            return $value.css('display', '');
-          }
-        });
-        addDueDate = function(isDropdown) {
-          var data, date, mediumDate, value;
-          // Create the post date
-          value = $("input[name='" + fieldName + "']").val();
-          if (value !== '') {
-            date = new Date(value.replace(new RegExp('-', 'g'), ','));
-            mediumDate = $.datepicker.formatDate('M d, yy', date);
-          } else {
-            mediumDate = 'No due date';
-          }
-          data = {};
-          data[abilityName] = {};
-          data[abilityName].due_date = value;
-          return $.ajax({
-            type: 'PUT',
-            url: issueUpdateURL,
-            data: data,
-            dataType: 'json',
-            beforeSend: function() {
-              var cssClass;
-              $loading.fadeIn();
-              if (isDropdown) {
-                $dropdown.trigger('loading.gl.dropdown');
-                $selectbox.hide();
-              }
-              $value.css('display', '');
-              cssClass = Date.parse(mediumDate) ? 'bold' : 'no-value';
-              $valueContent.html("<span class='" + cssClass + "'>" + mediumDate + "</span>");
-              $sidebarValue.html(mediumDate);
-              if (value !== '') {
-                return $('.js-remove-due-date-holder').removeClass('hidden');
-              } else {
-                return $('.js-remove-due-date-holder').addClass('hidden');
-              }
-            }
-          }).done(function(data) {
-            if (isDropdown) {
-              $dropdown.trigger('loaded.gl.dropdown');
-              $dropdown.dropdown('toggle');
-            }
-            return $loading.fadeOut();
-          });
-        };
-        $block.on('click', '.js-remove-due-date', function(e) {
-          e.preventDefault();
-          $("input[name='" + fieldName + "']").val('');
-          return addDueDate(false);
-        });
-        return $datePicker.datepicker({
-          dateFormat: 'yy-mm-dd',
-          defaultDate: $("input[name='" + fieldName + "']").val(),
-          altField: "input[name='" + fieldName + "']",
-          onSelect: function() {
-            return addDueDate(true);
-          }
-        });
-      });
-      $(document).off('click', '.ui-datepicker-header a').on('click', '.ui-datepicker-header a', function(e) {
-        return e.stopImmediatePropagation();
-      });
-    }
-
-    return DueDateSelect;
-
-  })();
-
-}).call(this);
diff --git a/app/assets/javascripts/due_date_select.js.es6 b/app/assets/javascripts/due_date_select.js.es6
new file mode 100644
index 0000000000000000000000000000000000000000..41925fcc8e3baaa0daa3b8bc21adc23619f00d05
--- /dev/null
+++ b/app/assets/javascripts/due_date_select.js.es6
@@ -0,0 +1,161 @@
+(function(global) {
+  class DueDateSelect {
+    constructor({ $dropdown, $loading } = {}) {
+      const $dropdownParent = $dropdown.closest('.dropdown');
+      const $block = $dropdown.closest('.block');
+      this.$loading = $loading;
+      this.$dropdown = $dropdown;
+      this.$dropdownParent = $dropdownParent;
+      this.$datePicker = $dropdownParent.find('.js-due-date-calendar');
+      this.$block = $block;
+      this.$selectbox = $dropdown.closest('.selectbox');
+      this.$value = $block.find('.value');
+      this.$valueContent = $block.find('.value-content');
+      this.$sidebarValue = $('.js-due-date-sidebar-value', $block);
+      this.fieldName = $dropdown.data('field-name'),
+      this.abilityName = $dropdown.data('ability-name'),
+      this.issueUpdateURL = $dropdown.data('issue-update')
+
+      this.rawSelectedDate = null;
+      this.displayedDate = null;
+      this.datePayload = null;
+
+      this.initGlDropdown();
+      this.initRemoveDueDate();
+      this.initDatePicker();
+      this.initStopPropagation();
+    }
+
+    initGlDropdown() {
+      this.$dropdown.glDropdown({
+        hidden: () => {
+          this.$selectbox.hide();
+          this.$value.css('display', '');
+        }
+      });
+    }
+
+    initDatePicker() {
+      this.$datePicker.datepicker({
+        dateFormat: 'yy-mm-dd',
+        defaultDate: $("input[name='" + this.fieldName + "']").val(),
+        altField: "input[name='" + this.fieldName + "']",
+        onSelect: () => {
+          return this.saveDueDate(true);
+        }
+      });
+    }
+
+    initRemoveDueDate() {
+      this.$block.on('click', '.js-remove-due-date', (e) => {
+        e.preventDefault();
+        $("input[name='" + this.fieldName + "']").val('');
+        return this.saveDueDate(false);
+      });
+    }
+
+    initStopPropagation() {
+      $(document).off('click', '.ui-datepicker-header a').on('click', '.ui-datepicker-header a', (e) => {
+        return e.stopImmediatePropagation();
+      });
+    }
+
+    saveDueDate(isDropdown) {
+      this.parseSelectedDate();
+      this.prepSelectedDate();
+      this.submitSelectedDate(isDropdown);
+    }
+
+    parseSelectedDate() {
+      this.rawSelectedDate = $("input[name='" + this.fieldName + "']").val();
+      if (this.rawSelectedDate.length) {
+        let dateObj = new Date(this.rawSelectedDate);
+        this.displayedDate = $.datepicker.formatDate('M d, yy', dateObj);
+      } else {
+        this.displayedDate = 'No due date';
+      }
+    }
+
+    prepSelectedDate() {
+      const datePayload = {};
+      datePayload[this.abilityName] = {};
+      datePayload[this.abilityName].due_date = this.rawSelectedDate;
+      this.datePayload = datePayload;
+    }
+
+    submitSelectedDate(isDropdown) {
+      return $.ajax({
+        type: 'PUT',
+        url: this.issueUpdateURL,
+        data: this.datePayload,
+        dataType: 'json',
+        beforeSend: () => {
+          const selectedDateValue = this.datePayload[this.abilityName].due_date;
+          const displayedDateStyle = this.displayedDate !== 'No due date' ? 'bold' : 'no-value';
+
+          this.$loading.fadeIn();
+
+          if (isDropdown) {
+            this.$dropdown.trigger('loading.gl.dropdown');
+            this.$selectbox.hide();
+          }
+
+          this.$value.css('display', '');
+          this.$valueContent.html(`<span class='${displayedDateStyle}'>${this.displayedDate}</span>`);
+          this.$sidebarValue.html(this.displayedDate);
+
+          return selectedDateValue.length ?
+            $('.js-remove-due-date-holder').removeClass('hidden') :
+            $('.js-remove-due-date-holder').addClass('hidden');
+
+        }
+      }).done((data) => {
+        if (isDropdown) {
+          this.$dropdown.trigger('loaded.gl.dropdown');
+          this.$dropdown.dropdown('toggle');
+        }
+        return this.$loading.fadeOut();
+      });
+    }
+  }
+
+  class DueDateSelectors {
+    constructor() {
+      this.initMilestoneDueDate();
+      this.initIssuableSelect();
+    }
+
+    initMilestoneDueDate() {
+      const $datePicker = $('.datepicker');
+
+      if ($datePicker.length) {
+        const $dueDate = $('#milestone_due_date');
+        $datePicker.datepicker({
+          dateFormat: 'yy-mm-dd',
+          onSelect: (dateText, inst) => {
+            $dueDate.val(dateText);
+          }
+        }).datepicker('setDate', $.datepicker.parseDate('yy-mm-dd', $dueDate.val()));
+      }
+      $('.js-clear-due-date').on('click', (e) => {
+        e.preventDefault();
+        $.datepicker._clearDate($datePicker);
+      });
+    }
+
+    initIssuableSelect() {
+      const $loading = $('.js-issuable-update .due_date').find('.block-loading').hide();
+
+      $('.js-due-date-select').each((i, dropdown) => {
+        const $dropdown = $(dropdown);
+        new DueDateSelect({
+          $dropdown,
+          $loading
+        });
+      });
+    }
+  }
+
+  global.DueDateSelectors = DueDateSelectors;
+
+})(window.gl || (window.gl = {}));
diff --git a/app/assets/javascripts/merge_conflict_data_provider.js.es6 b/app/assets/javascripts/merge_conflict_data_provider.js.es6
deleted file mode 100644
index 13ee794ba38523ffd1b28fad30ad15126017d3bb..0000000000000000000000000000000000000000
--- a/app/assets/javascripts/merge_conflict_data_provider.js.es6
+++ /dev/null
@@ -1,347 +0,0 @@
-const HEAD_HEADER_TEXT    = 'HEAD//our changes';
-const ORIGIN_HEADER_TEXT  = 'origin//their changes';
-const HEAD_BUTTON_TITLE   = 'Use ours';
-const ORIGIN_BUTTON_TITLE = 'Use theirs';
-
-
-class MergeConflictDataProvider {
-
-  getInitialData() {
-    // TODO: remove reliance on jQuery and DOM state introspection
-    const diffViewType = $.cookie('diff_view');
-    const fixedLayout = $('.content-wrapper .container-fluid').hasClass('container-limited');
-
-    return {
-      isLoading      : true,
-      hasError       : false,
-      isParallel     : diffViewType === 'parallel',
-      diffViewType   : diffViewType,
-      fixedLayout    : fixedLayout,
-      isSubmitting   : false,
-      conflictsData  : {},
-      resolutionData : {}
-    }
-  }
-
-
-  decorateData(vueInstance, data) {
-    this.vueInstance = vueInstance;
-
-    if (data.type === 'error') {
-      vueInstance.hasError = true;
-      data.errorMessage = data.message;
-    }
-    else {
-      data.shortCommitSha = data.commit_sha.slice(0, 7);
-      data.commitMessage  = data.commit_message;
-
-      this.setParallelLines(data);
-      this.setInlineLines(data);
-      this.updateResolutionsData(data);
-    }
-
-    vueInstance.conflictsData = data;
-    vueInstance.isSubmitting = false;
-
-    const conflictsText = this.getConflictsCount() > 1 ? 'conflicts' : 'conflict';
-    vueInstance.conflictsData.conflictsText = conflictsText;
-  }
-
-
-  updateResolutionsData(data) {
-    const vi = this.vueInstance;
-
-    data.files.forEach( (file) => {
-      file.sections.forEach( (section) => {
-        if (section.conflict) {
-          vi.$set(`resolutionData['${section.id}']`, false);
-        }
-      });
-    });
-  }
-
-
-  setParallelLines(data) {
-    data.files.forEach( (file) => {
-      file.filePath  = this.getFilePath(file);
-      file.iconClass = `fa-${file.blob_icon}`;
-      file.blobPath  = file.blob_path;
-      file.parallelLines = [];
-      const linesObj = { left: [], right: [] };
-
-      file.sections.forEach( (section) => {
-        const { conflict, lines, id } = section;
-
-        if (conflict) {
-          linesObj.left.push(this.getOriginHeaderLine(id));
-          linesObj.right.push(this.getHeadHeaderLine(id));
-        }
-
-        lines.forEach( (line) => {
-          const { type } = line;
-
-          if (conflict) {
-            if (type === 'old') {
-              linesObj.left.push(this.getLineForParallelView(line, id, 'conflict'));
-            }
-            else if (type === 'new') {
-              linesObj.right.push(this.getLineForParallelView(line, id, 'conflict', true));
-            }
-          }
-          else {
-            const lineType = type || 'context';
-
-            linesObj.left.push (this.getLineForParallelView(line, id, lineType));
-            linesObj.right.push(this.getLineForParallelView(line, id, lineType, true));
-          }
-        });
-
-        this.checkLineLengths(linesObj);
-      });
-
-      for (let i = 0, len = linesObj.left.length; i < len; i++) {
-        file.parallelLines.push([
-          linesObj.right[i],
-          linesObj.left[i]
-        ]);
-      }
-
-    });
-  }
-
-
-  checkLineLengths(linesObj) {
-    let { left, right } = linesObj;
-
-    if (left.length !== right.length) {
-      if (left.length > right.length) {
-        const diff = left.length - right.length;
-        for (let i = 0; i < diff; i++) {
-          right.push({ lineType: 'emptyLine', richText: '' });
-        }
-      }
-      else {
-        const diff = right.length - left.length;
-        for (let i = 0; i < diff; i++) {
-          left.push({ lineType: 'emptyLine', richText: '' });
-        }
-      }
-    }
-  }
-
-
-  setInlineLines(data) {
-    data.files.forEach( (file) => {
-      file.iconClass   = `fa-${file.blob_icon}`;
-      file.blobPath    = file.blob_path;
-      file.filePath    = this.getFilePath(file);
-      file.inlineLines = []
-
-      file.sections.forEach( (section) => {
-        let currentLineType = 'new';
-        const { conflict, lines, id } = section;
-
-        if (conflict) {
-          file.inlineLines.push(this.getHeadHeaderLine(id));
-        }
-
-        lines.forEach( (line) => {
-          const { type } = line;
-
-          if ((type === 'new' || type === 'old') && currentLineType !== type) {
-            currentLineType = type;
-            file.inlineLines.push({ lineType: 'emptyLine', richText: '' });
-          }
-
-          this.decorateLineForInlineView(line, id, conflict);
-          file.inlineLines.push(line);
-        })
-
-        if (conflict) {
-          file.inlineLines.push(this.getOriginHeaderLine(id));
-        }
-      });
-    });
-  }
-
-
-  handleSelected(sectionId, selection) {
-    const vi = this.vueInstance;
-
-    vi.resolutionData[sectionId] = selection;
-    vi.conflictsData.files.forEach( (file) => {
-      file.inlineLines.forEach( (line) => {
-        if (line.id === sectionId && (line.hasConflict || line.isHeader)) {
-          this.markLine(line, selection);
-        }
-      });
-
-      file.parallelLines.forEach( (lines) => {
-        const left         = lines[0];
-        const right        = lines[1];
-        const hasSameId    = right.id === sectionId || left.id === sectionId;
-        const isLeftMatch  = left.hasConflict || left.isHeader;
-        const isRightMatch = right.hasConflict || right.isHeader;
-
-        if (hasSameId && (isLeftMatch || isRightMatch)) {
-          this.markLine(left, selection);
-          this.markLine(right, selection);
-        }
-      })
-    });
-  }
-
-
-  updateViewType(newType) {
-    const vi = this.vueInstance;
-
-    if (newType === vi.diffViewType || !(newType === 'parallel' || newType === 'inline')) {
-      return;
-    }
-
-    vi.diffViewType = newType;
-    vi.isParallel   = newType === 'parallel';
-    $.cookie('diff_view', newType, {
-      path: (gon && gon.relative_url_root) || '/'
-    });
-    $('.content-wrapper .container-fluid')
-      .toggleClass('container-limited', !vi.isParallel && vi.fixedLayout);
-  }
-
-
-  markLine(line, selection) {
-    if (selection === 'head' && line.isHead) {
-      line.isSelected   = true;
-      line.isUnselected = false;
-    }
-    else if (selection === 'origin' && line.isOrigin) {
-      line.isSelected   = true;
-      line.isUnselected = false;
-    }
-    else {
-      line.isSelected   = false;
-      line.isUnselected = true;
-    }
-  }
-
-
-  getConflictsCount() {
-    return Object.keys(this.vueInstance.resolutionData).length;
-  }
-
-
-  getResolvedCount() {
-    let  count = 0;
-    const data = this.vueInstance.resolutionData;
-
-    for (const id in data) {
-      const resolution = data[id];
-      if (resolution) {
-        count++;
-      }
-    }
-
-    return count;
-  }
-
-
-  isReadyToCommit() {
-    const { conflictsData, isSubmitting } = this.vueInstance
-    const allResolved = this.getConflictsCount() === this.getResolvedCount();
-    const hasCommitMessage = $.trim(conflictsData.commitMessage).length;
-
-    return !isSubmitting && hasCommitMessage && allResolved;
-  }
-
-
-  getCommitButtonText() {
-    const initial = 'Commit conflict resolution';
-    const inProgress = 'Committing...';
-    const vue = this.vueInstance;
-
-    return vue ? vue.isSubmitting ? inProgress : initial : initial;
-  }
-
-
-  decorateLineForInlineView(line, id, conflict) {
-    const { type }    = line;
-    line.id           = id;
-    line.hasConflict  = conflict;
-    line.isHead       = type === 'new';
-    line.isOrigin     = type === 'old';
-    line.hasMatch     = type === 'match';
-    line.richText     = line.rich_text;
-    line.isSelected   = false;
-    line.isUnselected = false;
-  }
-
-  getLineForParallelView(line, id, lineType, isHead) {
-    const { old_line, new_line, rich_text } = line;
-    const hasConflict = lineType === 'conflict';
-
-    return {
-      id,
-      lineType,
-      hasConflict,
-      isHead       : hasConflict && isHead,
-      isOrigin     : hasConflict && !isHead,
-      hasMatch     : lineType === 'match',
-      lineNumber   : isHead ? new_line : old_line,
-      section      : isHead ? 'head' : 'origin',
-      richText     : rich_text,
-      isSelected   : false,
-      isUnselected : false
-    }
-  }
-
-
-  getHeadHeaderLine(id) {
-    return {
-      id          : id,
-      richText    : HEAD_HEADER_TEXT,
-      buttonTitle : HEAD_BUTTON_TITLE,
-      type        : 'new',
-      section     : 'head',
-      isHeader    : true,
-      isHead      : true,
-      isSelected  : false,
-      isUnselected: false
-    }
-  }
-
-
-  getOriginHeaderLine(id) {
-    return {
-      id          : id,
-      richText    : ORIGIN_HEADER_TEXT,
-      buttonTitle : ORIGIN_BUTTON_TITLE,
-      type        : 'old',
-      section     : 'origin',
-      isHeader    : true,
-      isOrigin    : true,
-      isSelected  : false,
-      isUnselected: false
-    }
-  }
-
-
-  handleFailedRequest(vueInstance, data) {
-    vueInstance.hasError = true;
-    vueInstance.conflictsData.errorMessage = 'Something went wrong!';
-  }
-
-
-  getCommitData() {
-    return {
-      commit_message: this.vueInstance.conflictsData.commitMessage,
-      sections: this.vueInstance.resolutionData
-    }
-  }
-
-
-  getFilePath(file) {
-    const { old_path, new_path } = file;
-    return old_path === new_path ? new_path : `${old_path} → ${new_path}`;
-  }
-
-}
diff --git a/app/assets/javascripts/merge_conflict_resolver.js.es6 b/app/assets/javascripts/merge_conflict_resolver.js.es6
deleted file mode 100644
index 7e756433bf5fb5687239a3be1f44e0fd4ea78f73..0000000000000000000000000000000000000000
--- a/app/assets/javascripts/merge_conflict_resolver.js.es6
+++ /dev/null
@@ -1,82 +0,0 @@
-//= require vue
-
-class MergeConflictResolver {
-
-  constructor() {
-    this.dataProvider = new MergeConflictDataProvider()
-    this.initVue()
-  }
-
-
-  initVue() {
-    const that = this;
-    this.vue   = new Vue({
-      el       : '#conflicts',
-      name     : 'MergeConflictResolver',
-      data     : this.dataProvider.getInitialData(),
-      created  : this.fetchData(),
-      computed : this.setComputedProperties(),
-      methods  : {
-        handleSelected(sectionId, selection) {
-          that.dataProvider.handleSelected(sectionId, selection);
-        },
-        handleViewTypeChange(newType) {
-          that.dataProvider.updateViewType(newType);
-        },
-        commit() {
-          that.commit();
-        }
-      }
-    })
-  }
-
-
-  setComputedProperties() {
-    const dp = this.dataProvider;
-
-    return {
-      conflictsCount() { return dp.getConflictsCount() },
-      resolvedCount() { return dp.getResolvedCount() },
-      readyToCommit() { return dp.isReadyToCommit() },
-      commitButtonText() { return dp.getCommitButtonText() }
-    }
-  }
-
-
-  fetchData() {
-    const dp = this.dataProvider;
-
-    $.get($('#conflicts').data('conflictsPath'))
-      .done((data) => {
-        dp.decorateData(this.vue, data);
-      })
-      .error((data) => {
-        dp.handleFailedRequest(this.vue, data);
-      })
-      .always(() => {
-        this.vue.isLoading = false;
-
-        this.vue.$nextTick(() => {
-          $('#conflicts .js-syntax-highlight').syntaxHighlight();
-        });
-
-        $('.content-wrapper .container-fluid')
-          .toggleClass('container-limited', !this.vue.isParallel && this.vue.fixedLayout);
-      })
-  }
-
-
-  commit() {
-    this.vue.isSubmitting = true;
-
-    $.post($('#conflicts').data('resolveConflictsPath'), this.dataProvider.getCommitData())
-      .done((data) => {
-        window.location.href = data.redirect_to;
-      })
-      .error(() => {
-        this.vue.isSubmitting = false;
-        new Flash('Something went wrong!');
-      });
-  }
-
-}
diff --git a/app/assets/javascripts/merge_conflicts/components/diff_file_editor.js.es6 b/app/assets/javascripts/merge_conflicts/components/diff_file_editor.js.es6
new file mode 100644
index 0000000000000000000000000000000000000000..5012bdfe9979fd671ec32f96bf55c70d3ea885f0
--- /dev/null
+++ b/app/assets/javascripts/merge_conflicts/components/diff_file_editor.js.es6
@@ -0,0 +1,93 @@
+((global) => {
+
+  global.mergeConflicts = global.mergeConflicts || {};
+
+  global.mergeConflicts.diffFileEditor = Vue.extend({
+    props: {
+      file: Object,
+      onCancelDiscardConfirmation: Function,
+      onAcceptDiscardConfirmation: Function
+    },
+    data() {
+      return {
+        saved: false,
+        loading: false,
+        fileLoaded: false,
+        originalContent: '',
+      }
+    },
+    computed: {
+      classObject() {
+        return {
+          'saved': this.saved,
+          'is-loading': this.loading
+        };
+      }
+    },
+    watch: {
+      ['file.showEditor'](val) {
+        this.resetEditorContent();
+
+        if (!val || this.fileLoaded || this.loading) {
+          return;
+        }
+
+        this.loadEditor();
+      }
+    },
+    ready() {
+      if (this.file.loadEditor) {
+        this.loadEditor();
+      }
+    },
+    methods: {
+      loadEditor() {
+        this.loading = true;
+
+        $.get(this.file.content_path)
+          .done((file) => {
+            let content = this.$el.querySelector('pre');
+            let fileContent = document.createTextNode(file.content);
+
+            content.textContent = fileContent.textContent;
+
+            this.originalContent = file.content;
+            this.fileLoaded = true;
+            this.editor = ace.edit(content);
+            this.editor.$blockScrolling = Infinity; // Turn off annoying warning
+            this.editor.getSession().setMode(`ace/mode/${file.blob_ace_mode}`);
+            this.editor.on('change', () => {
+              this.saveDiffResolution();
+            });
+            this.saveDiffResolution();
+          })
+          .fail(() => {
+            new Flash('Failed to load the file, please try again.');
+          })
+          .always(() => {
+            this.loading = false;
+          });
+      },
+      saveDiffResolution() {
+        this.saved = true;
+
+        // This probably be better placed in the data provider
+        this.file.content = this.editor.getValue();
+        this.file.resolveEditChanged = this.file.content !== this.originalContent;
+        this.file.promptDiscardConfirmation = false;
+      },
+      resetEditorContent() {
+        if (this.fileLoaded) {
+          this.editor.setValue(this.originalContent, -1);
+        }
+      },
+      cancelDiscardConfirmation(file) {
+        this.onCancelDiscardConfirmation(file);
+      },
+      acceptDiscardConfirmation(file) {
+        this.onAcceptDiscardConfirmation(file);
+      }
+    }
+  });
+
+})(window.gl || (window.gl = {}));
diff --git a/app/assets/javascripts/merge_conflicts/components/inline_conflict_lines.js.es6 b/app/assets/javascripts/merge_conflicts/components/inline_conflict_lines.js.es6
new file mode 100644
index 0000000000000000000000000000000000000000..b4be1c8988d0c6ce37548ede8d2329ed9092cc99
--- /dev/null
+++ b/app/assets/javascripts/merge_conflicts/components/inline_conflict_lines.js.es6
@@ -0,0 +1,12 @@
+((global) => {
+
+  global.mergeConflicts = global.mergeConflicts || {};
+
+  global.mergeConflicts.inlineConflictLines = Vue.extend({
+    props: {
+      file: Object
+    },
+    mixins: [global.mergeConflicts.utils, global.mergeConflicts.actions],
+  });
+
+})(window.gl || (window.gl = {}));
diff --git a/app/assets/javascripts/merge_conflicts/components/parallel_conflict_line.js.es6 b/app/assets/javascripts/merge_conflicts/components/parallel_conflict_line.js.es6
new file mode 100644
index 0000000000000000000000000000000000000000..8b0a8ab20731215a5f521887d1b6fa6cbb9115de
--- /dev/null
+++ b/app/assets/javascripts/merge_conflicts/components/parallel_conflict_line.js.es6
@@ -0,0 +1,14 @@
+((global) => {
+
+  global.mergeConflicts = global.mergeConflicts || {};
+
+  global.mergeConflicts.parallelConflictLine = Vue.extend({
+    props: {
+      file: Object,
+      line: Object
+    },
+    mixins: [global.mergeConflicts.utils, global.mergeConflicts.actions],
+    template: '#parallel-conflict-line'
+  });
+
+})(window.gl || (window.gl = {}));
diff --git a/app/assets/javascripts/merge_conflicts/components/parallel_conflict_lines.js.es6 b/app/assets/javascripts/merge_conflicts/components/parallel_conflict_lines.js.es6
new file mode 100644
index 0000000000000000000000000000000000000000..eb4cc6a9dac9686480f3dd35fe19405d43cd4858
--- /dev/null
+++ b/app/assets/javascripts/merge_conflicts/components/parallel_conflict_lines.js.es6
@@ -0,0 +1,15 @@
+((global) => {
+
+  global.mergeConflicts = global.mergeConflicts || {};
+
+  global.mergeConflicts.parallelConflictLines = Vue.extend({
+    props: {
+      file: Object
+    },
+    mixins: [global.mergeConflicts.utils],
+    components: {
+      'parallel-conflict-line': gl.mergeConflicts.parallelConflictLine
+    }
+  });
+
+})(window.gl || (window.gl = {}));
diff --git a/app/assets/javascripts/merge_conflicts/merge_conflict_service.js.es6 b/app/assets/javascripts/merge_conflicts/merge_conflict_service.js.es6
new file mode 100644
index 0000000000000000000000000000000000000000..da2fb8b1323657930e7de069248ca7bcc1ad74cd
--- /dev/null
+++ b/app/assets/javascripts/merge_conflicts/merge_conflict_service.js.es6
@@ -0,0 +1,30 @@
+((global) => {
+  global.mergeConflicts = global.mergeConflicts || {};
+
+  class mergeConflictsService {
+    constructor(options) {
+      this.conflictsPath = options.conflictsPath;
+      this.resolveConflictsPath = options.resolveConflictsPath;
+    }
+
+    fetchConflictsData() {
+      return $.ajax({
+        dataType: 'json',
+        url: this.conflictsPath
+      });
+    }
+
+    submitResolveConflicts(data) {
+      return $.ajax({
+        url: this.resolveConflictsPath,
+        data: JSON.stringify(data),
+        contentType: 'application/json',
+        dataType: 'json',
+        method: 'POST'
+      });
+    }
+  };
+
+  global.mergeConflicts.mergeConflictsService = mergeConflictsService;
+
+})(window.gl || (window.gl = {}));
diff --git a/app/assets/javascripts/merge_conflicts/merge_conflict_store.js.es6 b/app/assets/javascripts/merge_conflicts/merge_conflict_store.js.es6
new file mode 100644
index 0000000000000000000000000000000000000000..5c5c65f29d4fbb5d7fa17545bbb1a068dc03d3b6
--- /dev/null
+++ b/app/assets/javascripts/merge_conflicts/merge_conflict_store.js.es6
@@ -0,0 +1,437 @@
+((global) => {
+  global.mergeConflicts = global.mergeConflicts || {};
+
+  const diffViewType = $.cookie('diff_view');
+  const HEAD_HEADER_TEXT = 'HEAD//our changes';
+  const ORIGIN_HEADER_TEXT = 'origin//their changes';
+  const HEAD_BUTTON_TITLE = 'Use ours';
+  const ORIGIN_BUTTON_TITLE = 'Use theirs';
+  const INTERACTIVE_RESOLVE_MODE = 'interactive';
+  const EDIT_RESOLVE_MODE = 'edit';
+  const DEFAULT_RESOLVE_MODE = INTERACTIVE_RESOLVE_MODE;
+  const VIEW_TYPES = {
+    INLINE: 'inline',
+    PARALLEL: 'parallel'
+  };
+  const CONFLICT_TYPES = {
+    TEXT: 'text',
+    TEXT_EDITOR: 'text-editor'
+  };
+
+  global.mergeConflicts.mergeConflictsStore = {
+    state: {
+      isLoading: true,
+      hasError: false,
+      isSubmitting: false,
+      isParallel: diffViewType === VIEW_TYPES.PARALLEL,
+      diffViewType: diffViewType,
+      conflictsData: {}
+    },
+
+    setConflictsData(data) {
+      this.decorateFiles(data.files);
+
+      this.state.conflictsData = {
+        files: data.files,
+        commitMessage: data.commit_message,
+        sourceBranch: data.source_branch,
+        targetBranch: data.target_branch,
+        commitMessage: data.commit_message,
+        shortCommitSha: data.commit_sha.slice(0, 7),
+      };
+    },
+
+    decorateFiles(files) {
+      files.forEach((file) => {
+        file.content = '';
+        file.resolutionData = {};
+        file.promptDiscardConfirmation = false;
+        file.resolveMode = DEFAULT_RESOLVE_MODE;
+        file.filePath = this.getFilePath(file);
+        file.iconClass = `fa-${file.blob_icon}`;
+        file.blobPath = file.blob_path;
+
+        if (file.type === CONFLICT_TYPES.TEXT) {
+          file.showEditor = false;
+          file.loadEditor = false;
+
+          this.setInlineLine(file);
+          this.setParallelLine(file);
+        } else if (file.type === CONFLICT_TYPES.TEXT_EDITOR) {
+          file.showEditor = true;
+          file.loadEditor = true;
+        }
+      });
+    },
+
+    setInlineLine(file) {
+      file.inlineLines = [];
+
+      file.sections.forEach((section) => {
+        let currentLineType = 'new';
+        const { conflict, lines, id } = section;
+
+        if (conflict) {
+          file.inlineLines.push(this.getHeadHeaderLine(id));
+        }
+
+        lines.forEach((line) => {
+          const { type } = line;
+
+          if ((type === 'new' || type === 'old') && currentLineType !== type) {
+            currentLineType = type;
+            file.inlineLines.push({ lineType: 'emptyLine', richText: '' });
+          }
+
+          this.decorateLineForInlineView(line, id, conflict);
+          file.inlineLines.push(line);
+        })
+
+        if (conflict) {
+          file.inlineLines.push(this.getOriginHeaderLine(id));
+        }
+      });
+    },
+
+    setParallelLine(file) {
+      file.parallelLines = [];
+      const linesObj = { left: [], right: [] };
+
+      file.sections.forEach((section) => {
+        const { conflict, lines, id } = section;
+
+        if (conflict) {
+          linesObj.left.push(this.getOriginHeaderLine(id));
+          linesObj.right.push(this.getHeadHeaderLine(id));
+        }
+
+        lines.forEach((line) => {
+          const { type } = line;
+
+          if (conflict) {
+            if (type === 'old') {
+              linesObj.left.push(this.getLineForParallelView(line, id, 'conflict'));
+            } else if (type === 'new') {
+              linesObj.right.push(this.getLineForParallelView(line, id, 'conflict', true));
+            }
+          } else {
+            const lineType = type || 'context';
+
+            linesObj.left.push (this.getLineForParallelView(line, id, lineType));
+            linesObj.right.push(this.getLineForParallelView(line, id, lineType, true));
+          }
+        });
+
+        this.checkLineLengths(linesObj);
+      });
+
+      for (let i = 0, len = linesObj.left.length; i < len; i++) {
+        file.parallelLines.push([
+          linesObj.right[i],
+          linesObj.left[i]
+        ]);
+      }
+    },
+
+    setLoadingState(state) {
+      this.state.isLoading = state;
+    },
+
+    setErrorState(state) {
+      this.state.hasError = state;
+    },
+
+    setFailedRequest(message) {
+      this.state.hasError = true;
+      this.state.conflictsData.errorMessage = message;
+    },
+
+    getConflictsCount() {
+      if (!this.state.conflictsData.files.length) {
+        return 0;
+      }
+
+      const files = this.state.conflictsData.files;
+      let count = 0;
+
+      files.forEach((file) => {
+        if (file.type === CONFLICT_TYPES.TEXT) {
+          file.sections.forEach((section) => {
+            if (section.conflict) {
+              count++;
+            }
+          });
+        } else {
+          count++;
+        }
+      });
+
+      return count;
+    },
+
+    getConflictsCountText() {
+      const count = this.getConflictsCount();
+      const text = count ? 'conflicts' : 'conflict';
+
+      return `${count} ${text}`;
+    },
+
+    setViewType(viewType) {
+      this.state.diffView = viewType;
+      this.state.isParallel = viewType === VIEW_TYPES.PARALLEL;
+
+      $.cookie('diff_view', viewType, {
+        path: gon.relative_url_root || '/'
+      });
+    },
+
+    getHeadHeaderLine(id) {
+      return {
+        id: id,
+        richText: HEAD_HEADER_TEXT,
+        buttonTitle: HEAD_BUTTON_TITLE,
+        type: 'new',
+        section: 'head',
+        isHeader: true,
+        isHead: true,
+        isSelected: false,
+        isUnselected: false
+      };
+    },
+
+    decorateLineForInlineView(line, id, conflict) {
+      const { type } = line;
+      line.id = id;
+      line.hasConflict = conflict;
+      line.isHead = type === 'new';
+      line.isOrigin = type === 'old';
+      line.hasMatch = type === 'match';
+      line.richText = line.rich_text;
+      line.isSelected = false;
+      line.isUnselected = false;
+    },
+
+    getLineForParallelView(line, id, lineType, isHead) {
+      const { old_line, new_line, rich_text } = line;
+      const hasConflict = lineType === 'conflict';
+
+      return {
+        id,
+        lineType,
+        hasConflict,
+        isHead: hasConflict && isHead,
+        isOrigin: hasConflict && !isHead,
+        hasMatch: lineType === 'match',
+        lineNumber: isHead ? new_line : old_line,
+        section: isHead ? 'head' : 'origin',
+        richText: rich_text,
+        isSelected: false,
+        isUnselected: false
+      };
+    },
+
+    getOriginHeaderLine(id) {
+      return {
+        id: id,
+        richText: ORIGIN_HEADER_TEXT,
+        buttonTitle: ORIGIN_BUTTON_TITLE,
+        type: 'old',
+        section: 'origin',
+        isHeader: true,
+        isOrigin: true,
+        isSelected: false,
+        isUnselected: false
+      };
+    },
+
+    getFilePath(file) {
+      const { old_path, new_path } = file;
+      return old_path === new_path ? new_path : `${old_path} → ${new_path}`;
+    },
+
+    checkLineLengths(linesObj) {
+      let { left, right } = linesObj;
+
+      if (left.length !== right.length) {
+        if (left.length > right.length) {
+          const diff = left.length - right.length;
+          for (let i = 0; i < diff; i++) {
+            right.push({ lineType: 'emptyLine', richText: '' });
+          }
+        } else {
+          const diff = right.length - left.length;
+          for (let i = 0; i < diff; i++) {
+            left.push({ lineType: 'emptyLine', richText: '' });
+          }
+        }
+      }
+    },
+
+    setPromptConfirmationState(file, state) {
+      file.promptDiscardConfirmation = state;
+    },
+
+    setFileResolveMode(file, mode) {
+      if (mode === INTERACTIVE_RESOLVE_MODE) {
+        file.showEditor = false;
+      } else if (mode === EDIT_RESOLVE_MODE) {
+        // Restore Interactive mode when switching to Edit mode
+        file.showEditor = true;
+        file.loadEditor = true;
+        file.resolutionData = {};
+
+        this.restoreFileLinesState(file);
+      }
+
+      file.resolveMode = mode;
+    },
+
+    restoreFileLinesState(file) {
+      file.inlineLines.forEach((line) => {
+        if (line.hasConflict || line.isHeader) {
+          line.isSelected = false;
+          line.isUnselected = false;
+        }
+      });
+
+      file.parallelLines.forEach((lines) => {
+        const left = lines[0];
+        const right = lines[1];
+        const isLeftMatch = left.hasConflict || left.isHeader;
+        const isRightMatch = right.hasConflict || right.isHeader;
+
+        if (isLeftMatch || isRightMatch) {
+          left.isSelected = false;
+          left.isUnselected = false;
+          right.isSelected = false;
+          right.isUnselected = false;
+        }
+      });
+    },
+
+    isReadyToCommit() {
+      const files = this.state.conflictsData.files;
+      const hasCommitMessage = $.trim(this.state.conflictsData.commitMessage).length;
+      let unresolved = 0;
+
+      for (let i = 0, l = files.length; i < l; i++) {
+        let file = files[i];
+
+        if (file.resolveMode === INTERACTIVE_RESOLVE_MODE) {
+          let numberConflicts = 0;
+          let resolvedConflicts = Object.keys(file.resolutionData).length
+
+          // We only check for conflicts type 'text'
+          // since conflicts `text_editor` can´t be resolved in interactive mode
+          if (file.type === CONFLICT_TYPES.TEXT) {
+            for (let j = 0, k = file.sections.length; j < k; j++) {
+              if (file.sections[j].conflict) {
+                numberConflicts++;
+              }
+            }
+
+            if (resolvedConflicts !== numberConflicts) {
+              unresolved++;
+            }
+          }
+        } else if (file.resolveMode === EDIT_RESOLVE_MODE) {
+
+          // Unlikely to happen since switching to Edit mode saves content automatically.
+          // Checking anyway in case the save strategy changes in the future
+          if (!file.content) {
+            unresolved++;
+            continue;
+          }
+        }
+      }
+
+      return !this.state.isSubmitting && hasCommitMessage && !unresolved;
+    },
+
+    getCommitButtonText() {
+      const initial = 'Commit conflict resolution';
+      const inProgress = 'Committing...';
+
+      return this.state ? this.state.isSubmitting ? inProgress : initial : initial;
+    },
+
+    getCommitData() {
+      let commitData = {};
+
+      commitData = {
+        commit_message: this.state.conflictsData.commitMessage,
+        files: []
+      };
+
+      this.state.conflictsData.files.forEach((file) => {
+        let addFile;
+
+        addFile = {
+          old_path: file.old_path,
+          new_path: file.new_path
+        };
+
+        if (file.type === CONFLICT_TYPES.TEXT) {
+
+          // Submit only one data for type of editing
+          if (file.resolveMode === INTERACTIVE_RESOLVE_MODE) {
+            addFile.sections = file.resolutionData;
+          } else if (file.resolveMode === EDIT_RESOLVE_MODE) {
+            addFile.content = file.content;
+          }
+        } else if (file.type === CONFLICT_TYPES.TEXT_EDITOR) {
+          addFile.content = file.content;
+        }
+
+        commitData.files.push(addFile);
+      });
+
+      return commitData;
+    },
+
+    handleSelected(file, sectionId, selection) {
+      Vue.set(file.resolutionData, sectionId, selection);
+
+      file.inlineLines.forEach((line) => {
+        if (line.id === sectionId && (line.hasConflict || line.isHeader)) {
+          this.markLine(line, selection);
+        }
+      });
+
+      file.parallelLines.forEach((lines) => {
+        const left = lines[0];
+        const right = lines[1];
+        const hasSameId = right.id === sectionId || left.id === sectionId;
+        const isLeftMatch = left.hasConflict || left.isHeader;
+        const isRightMatch = right.hasConflict || right.isHeader;
+
+        if (hasSameId && (isLeftMatch || isRightMatch)) {
+          this.markLine(left, selection);
+          this.markLine(right, selection);
+        }
+      });
+    },
+
+    markLine(line, selection) {
+      if (selection === 'head' && line.isHead) {
+        line.isSelected = true;
+        line.isUnselected = false;
+      } else if (selection === 'origin' && line.isOrigin) {
+        line.isSelected = true;
+        line.isUnselected = false;
+      } else {
+        line.isSelected = false;
+        line.isUnselected = true;
+      }
+    },
+
+    setSubmitState(state) {
+      this.state.isSubmitting = state;
+    },
+
+    fileTextTypePresent() {
+      return this.state.conflictsData.files.some(f => f.type === CONFLICT_TYPES.TEXT);
+    }
+  };
+
+})(window.gl || (window.gl = {}));
diff --git a/app/assets/javascripts/merge_conflicts/merge_conflicts_bundle.js.es6 b/app/assets/javascripts/merge_conflicts/merge_conflicts_bundle.js.es6
new file mode 100644
index 0000000000000000000000000000000000000000..7fd3749b3e21f4a656a800d0c9ae655e277d6431
--- /dev/null
+++ b/app/assets/javascripts/merge_conflicts/merge_conflicts_bundle.js.es6
@@ -0,0 +1,89 @@
+//= require vue
+//= require ./merge_conflict_store
+//= require ./merge_conflict_service
+//= require ./mixins/line_conflict_utils
+//= require ./mixins/line_conflict_actions
+//= require ./components/diff_file_editor
+//= require ./components/inline_conflict_lines
+//= require ./components/parallel_conflict_line
+//= require ./components/parallel_conflict_lines
+
+$(() => {
+  const INTERACTIVE_RESOLVE_MODE = 'interactive';
+  const conflictsEl = document.querySelector('#conflicts');
+  const mergeConflictsStore = gl.mergeConflicts.mergeConflictsStore;
+  const mergeConflictsService = new gl.mergeConflicts.mergeConflictsService({
+    conflictsPath: conflictsEl.dataset.conflictsPath,
+    resolveConflictsPath: conflictsEl.dataset.resolveConflictsPath
+  });
+
+  gl.MergeConflictsResolverApp = new Vue({
+    el: '#conflicts',
+    data: mergeConflictsStore.state,
+    components: {
+      'diff-file-editor': gl.mergeConflicts.diffFileEditor,
+      'inline-conflict-lines': gl.mergeConflicts.inlineConflictLines,
+      'parallel-conflict-lines': gl.mergeConflicts.parallelConflictLines
+    },
+    computed: {
+      conflictsCountText() { return mergeConflictsStore.getConflictsCountText() },
+      readyToCommit() { return mergeConflictsStore.isReadyToCommit() },
+      commitButtonText() { return mergeConflictsStore.getCommitButtonText() },
+      showDiffViewTypeSwitcher() { return mergeConflictsStore.fileTextTypePresent() }
+    },
+    created() {
+      mergeConflictsService
+        .fetchConflictsData()
+        .done((data) => {
+          if (data.type === 'error') {
+            mergeConflictsStore.setFailedRequest(data.message);
+          } else {
+            mergeConflictsStore.setConflictsData(data);
+          }
+        })
+        .error(() => {
+          mergeConflictsStore.setFailedRequest();
+        })
+        .always(() => {
+          mergeConflictsStore.setLoadingState(false);
+
+          this.$nextTick(() => {
+            $(conflictsEl.querySelectorAll('.js-syntax-highlight')).syntaxHighlight();
+          });
+        });
+    },
+    methods: {
+      handleViewTypeChange(viewType) {
+        mergeConflictsStore.setViewType(viewType);
+      },
+      onClickResolveModeButton(file, mode) {
+        if (mode === INTERACTIVE_RESOLVE_MODE && file.resolveEditChanged) {
+          mergeConflictsStore.setPromptConfirmationState(file, true);
+          return;
+        }
+
+        mergeConflictsStore.setFileResolveMode(file, mode);
+      },
+      acceptDiscardConfirmation(file) {
+        mergeConflictsStore.setPromptConfirmationState(file, false);
+        mergeConflictsStore.setFileResolveMode(file, INTERACTIVE_RESOLVE_MODE);
+      },
+      cancelDiscardConfirmation(file) {
+        mergeConflictsStore.setPromptConfirmationState(file, false);
+      },
+      commit() {
+        mergeConflictsStore.setSubmitState(true);
+
+        mergeConflictsService
+          .submitResolveConflicts(mergeConflictsStore.getCommitData())
+          .done((data) => {
+            window.location.href = data.redirect_to;
+          })
+          .error(() => {
+            mergeConflictsStore.setSubmitState(false);
+            new Flash('Failed to save merge conflicts resolutions. Please try again!');
+          });
+      }
+    }
+  })
+});
diff --git a/app/assets/javascripts/merge_conflicts/mixins/line_conflict_actions.js.es6 b/app/assets/javascripts/merge_conflicts/mixins/line_conflict_actions.js.es6
new file mode 100644
index 0000000000000000000000000000000000000000..114a2c5b3055e7890c8244d0ee9bad8817edefb3
--- /dev/null
+++ b/app/assets/javascripts/merge_conflicts/mixins/line_conflict_actions.js.es6
@@ -0,0 +1,12 @@
+((global) => {
+  global.mergeConflicts = global.mergeConflicts || {};
+
+  global.mergeConflicts.actions = {
+    methods: {
+      handleSelected(file, sectionId, selection) {
+        gl.mergeConflicts.mergeConflictsStore.handleSelected(file, sectionId, selection);
+      }
+    }
+  };
+
+})(window.gl || (window.gl = {}));
diff --git a/app/assets/javascripts/merge_conflicts/mixins/line_conflict_utils.js.es6 b/app/assets/javascripts/merge_conflicts/mixins/line_conflict_utils.js.es6
new file mode 100644
index 0000000000000000000000000000000000000000..b846a90ab2afec062292b20463991fcfe0a296d3
--- /dev/null
+++ b/app/assets/javascripts/merge_conflicts/mixins/line_conflict_utils.js.es6
@@ -0,0 +1,18 @@
+((global) => {
+  global.mergeConflicts = global.mergeConflicts || {};
+
+  global.mergeConflicts.utils = {
+    methods: {
+      lineCssClass(line) {
+        return {
+          'head': line.isHead,
+          'origin': line.isOrigin,
+          'match': line.hasMatch,
+          'selected': line.isSelected,
+          'unselected': line.isUnselected
+        };
+      }
+    }
+  };
+
+})(window.gl || (window.gl = {}));
diff --git a/app/assets/javascripts/pipelines.js.es6 b/app/assets/javascripts/pipelines.js.es6
index 6bf63ee6979173f87dabba760db933a083f62698..a7624de6089f17a77eab7e8f2daa3b4116655ef1 100644
--- a/app/assets/javascripts/pipelines.js.es6
+++ b/app/assets/javascripts/pipelines.js.es6
@@ -15,7 +15,7 @@
       $($pipelineBtn).add($pipelineGraph).toggleClass('graph-collapsed');
 
 
-      graphCollapsed ? $btnText.text('Expand') : $btnText.text('Hide')
+      graphCollapsed ? $btnText.text('Hide') : $btnText.text('Expand')
     }
 
     addMarginToBuildColumns() {
diff --git a/app/assets/javascripts/project_find_file.js b/app/assets/javascripts/project_find_file.js
index 8e38ccf7e44ee15d9c4f2d656333b878a654901d..b8347367717f4975d4d52f9b209e22c951038b81 100644
--- a/app/assets/javascripts/project_find_file.js
+++ b/app/assets/javascripts/project_find_file.js
@@ -7,6 +7,7 @@
     function ProjectFindFile(element1, options) {
       this.element = element1;
       this.options = options;
+      this.goToBlob = bind(this.goToBlob, this);
       this.goToTree = bind(this.goToTree, this);
       this.selectRowDown = bind(this.selectRowDown, this);
       this.selectRowUp = bind(this.selectRowUp, this);
@@ -154,6 +155,14 @@
       return location.href = this.options.treeUrl;
     };
 
+    ProjectFindFile.prototype.goToBlob = function() {
+      var $link = this.element.find(".tree-item.selected .tree-item-file-name a");
+
+      if ($link.length) {
+        $link.get(0).click();
+      }
+    };
+
     return ProjectFindFile;
 
   })();
diff --git a/app/assets/javascripts/username_validator.js.es6 b/app/assets/javascripts/username_validator.js.es6
index 2517f778365001618aac5bb2176637d573b3fc31..bf4b2e320cd5980f04dddeffac6d370f778155fd 100644
--- a/app/assets/javascripts/username_validator.js.es6
+++ b/app/assets/javascripts/username_validator.js.es6
@@ -76,7 +76,7 @@
         this.renderState();
         return $.ajax({
           type: 'GET',
-          url: `/u/${username}/exists`,
+          url: `/users/${username}/exists`,
           dataType: 'json',
           success: (res) => this.setAvailabilityState(res.exists)
         });
diff --git a/app/assets/stylesheets/framework/typography.scss b/app/assets/stylesheets/framework/typography.scss
index 8df0067fac175ce45f751f44f1d1ab42b0946307..55de9053be581961f058fe493a459ad847dcf434 100644
--- a/app/assets/stylesheets/framework/typography.scss
+++ b/app/assets/stylesheets/framework/typography.scss
@@ -45,40 +45,38 @@
   }
 
   h1 {
-    font-size: 2em;
+    font-size: 1.75em;
     font-weight: 600;
-    margin: 1em 0 10px;
+    margin: 16px 0 10px;
     padding: 0 0 0.3em;
-    border-bottom: 1px solid $btn-default-border;
+    border-bottom: 1px solid $white-dark;
     color: $gl-gray-dark;
   }
 
   h2 {
-    font-size: 1.6em;
+    font-size: 1.5em;
     font-weight: 600;
-    margin: 1em 0 10px;
-    padding-bottom: 0.3em;
-    border-bottom: 1px solid $btn-default-border;
+    margin: 16px 0 10px;
     color: $gl-gray-dark;
   }
 
   h3 {
-    margin: 1em 0 10px;
-    font-size: 1.4em;
+    margin: 16px 0 10px;
+    font-size: 1.3em;
   }
 
   h4 {
-    margin: 1em 0 10px;
-    font-size: 1.25em;
+    margin: 16px 0 10px;
+    font-size: 1.2em;
   }
 
   h5 {
-    margin: 1em 0 10px;
+    margin: 16px 0 10px;
     font-size: 1em;
   }
 
   h6 {
-    margin: 1em 0 10px;
+    margin: 16px 0 10px;
     font-size: 0.95em;
   }
 
@@ -87,7 +85,12 @@
     font-size: inherit;
     padding: 8px 21px;
     margin: 12px 0;
-    border-left: 3px solid #e7e9ed;
+    border-left: 3px solid $white-dark;
+  }
+
+  blockquote:dir(rtl) {
+    border-left: 0;
+    border-right: 3px solid $white-dark;
   }
 
   blockquote p {
@@ -112,6 +115,10 @@
     }
   }
 
+  table:dir(rtl) th {
+    text-align: right;
+  }
+
   pre {
     margin: 12px 0;
     font-size: 13px;
@@ -129,6 +136,10 @@
     margin: 3px 0 3px 28px !important;
   }
 
+  ul:dir(rtl), ol:dir(rtl) {
+    margin: 3px 28px 3px 0 !important;
+  }
+
   li {
     line-height: 1.6em;
   }
diff --git a/app/assets/stylesheets/framework/variables.scss b/app/assets/stylesheets/framework/variables.scss
index 4c34ed3ebf78d71f1560151bcbf11db680b9cafe..7690d65de8e86595b4d3c64085dff2498a2c6d56 100644
--- a/app/assets/stylesheets/framework/variables.scss
+++ b/app/assets/stylesheets/framework/variables.scss
@@ -56,6 +56,7 @@ $border-gray-light: #dcdcdc;
 $border-gray-normal: #d7d7d7;
 $border-gray-dark: #c6cacf;
 
+$border-green-extra-light: #9adb84;
 $border-green-light: #2faa60;
 $border-green-normal: #2ca05b;
 $border-green-dark: #279654;
diff --git a/app/assets/stylesheets/pages/detail_page.scss b/app/assets/stylesheets/pages/detail_page.scss
index 4d9c73c6840a02456836801acc3d3a70c03d0692..2357671c2aeb731c838f5061469f9ad4036f75dd 100644
--- a/app/assets/stylesheets/pages/detail_page.scss
+++ b/app/assets/stylesheets/pages/detail_page.scss
@@ -20,9 +20,11 @@
 
 .detail-page-description {
   .title {
-    margin: 0;
-    font-size: 23px;
+    margin: 0 0 16px;
+    font-size: 2em;
     color: $gl-gray-dark;
+    padding: 0 0 0.3em;
+    border-bottom: 1px solid $white-dark;
   }
 
   .description {
diff --git a/app/assets/stylesheets/pages/merge_conflicts.scss b/app/assets/stylesheets/pages/merge_conflicts.scss
index 49013d7cac9ada5416537fe26b6e8ebcbc756393..eed2b0ab7ccc961d257d3bddb0be096ebcd89e74 100644
--- a/app/assets/stylesheets/pages/merge_conflicts.scss
+++ b/app/assets/stylesheets/pages/merge_conflicts.scss
@@ -237,4 +237,51 @@ $colors: (
   .btn-success .fa-spinner {
     color: #fff;
   }
+
+  .editor-wrap {
+    &.is-loading {
+      .editor {
+        display: none;
+      }
+
+      .loading {
+        display: block;
+      }
+    }
+
+    &.saved {
+      .editor {
+        border-top: solid 2px $border-green-extra-light;
+      }
+    }
+    
+    .editor {
+      pre {
+        height: 350px;
+        border: none;
+        border-radius: 0;
+        margin-bottom: 0;
+      }
+    }
+
+    .loading {
+      display: none;
+    }
+  }
+
+  .discard-changes-alert {
+    background-color: $background-color;
+    text-align: right;
+    padding: $gl-padding-top $gl-padding;
+    color: $gl-text-color;
+
+    .discard-actions {
+      display: inline-block;
+      margin-left: 10px;
+    }
+  }
+
+  .resolve-conflicts-form {
+    padding-top: $gl-padding;
+  }
 }
diff --git a/app/assets/stylesheets/pages/merge_requests.scss b/app/assets/stylesheets/pages/merge_requests.scss
index afc4e517fde3f556cfcb8c03118b8131e644e44d..101472278e26f177635d74f2535386ddd9a80374 100644
--- a/app/assets/stylesheets/pages/merge_requests.scss
+++ b/app/assets/stylesheets/pages/merge_requests.scss
@@ -429,13 +429,6 @@
   }
 }
 
-.merge-request-details {
-
-  .title {
-    margin-bottom: 20px;
-  }
-}
-
 .merge-request-tabs {
   background-color: #fff;
 
diff --git a/app/assets/stylesheets/pages/tree.scss b/app/assets/stylesheets/pages/tree.scss
index 99c0f6362d0bd1230a0216066ceadc6c53546f8b..6ea7a2b5498bd08ad5b794fe95e8e21764d757fd 100644
--- a/app/assets/stylesheets/pages/tree.scss
+++ b/app/assets/stylesheets/pages/tree.scss
@@ -169,4 +169,8 @@
   margin-top: 11px;
   position: relative;
   z-index: 2;
+
+  .download-button {
+    margin-left: $btn-side-margin;
+  }
 }
diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb
index 705824502eb971a698e9df4a050751cda66c61c9..37600ed875c2537653df0f3e85685306096687a8 100644
--- a/app/controllers/application_controller.rb
+++ b/app/controllers/application_controller.rb
@@ -118,7 +118,12 @@ class ApplicationController < ActionController::Base
   end
 
   def render_404
-    render file: Rails.root.join("public", "404"), layout: false, status: "404"
+    respond_to do |format|
+      format.html do
+        render file: Rails.root.join("public", "404"), layout: false, status: "404"
+      end
+      format.any { head :not_found }
+    end
   end
 
   def no_cache_headers
diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb
index 9207c954335cb94cc1965b150a2eeebaa25bc850..a39b47b6d9586d7327a6579427c55e7c6671f3f8 100644
--- a/app/controllers/projects/merge_requests_controller.rb
+++ b/app/controllers/projects/merge_requests_controller.rb
@@ -9,15 +9,15 @@ class Projects::MergeRequestsController < Projects::ApplicationController
 
   before_action :module_enabled
   before_action :merge_request, only: [
-    :edit, :update, :show, :diffs, :commits, :conflicts, :builds, :pipelines, :merge, :merge_check,
+    :edit, :update, :show, :diffs, :commits, :conflicts, :conflict_for_path, :builds, :pipelines, :merge, :merge_check,
     :ci_status, :ci_environments_status, :toggle_subscription, :cancel_merge_when_build_succeeds, :remove_wip, :resolve_conflicts, :assign_related_issues
   ]
   before_action :validates_merge_request, only: [:show, :diffs, :commits, :builds, :pipelines]
-  before_action :define_show_vars, only: [:show, :diffs, :commits, :conflicts, :builds, :pipelines]
+  before_action :define_show_vars, only: [:show, :diffs, :commits, :conflicts, :conflict_for_path, :builds, :pipelines]
   before_action :define_widget_vars, only: [:merge, :cancel_merge_when_build_succeeds, :merge_check]
   before_action :define_commit_vars, only: [:diffs]
   before_action :define_diff_comment_vars, only: [:diffs]
-  before_action :ensure_ref_fetched, only: [:show, :diffs, :commits, :builds, :conflicts, :pipelines]
+  before_action :ensure_ref_fetched, only: [:show, :diffs, :commits, :builds, :conflicts, :conflict_for_path, :pipelines]
   before_action :close_merge_request_without_source_project, only: [:show, :diffs, :commits, :builds, :pipelines]
   before_action :apply_diff_view_cookie!, only: [:new_diffs]
   before_action :build_merge_request, only: [:new, :new_diffs]
@@ -33,7 +33,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController
 
   before_action :authenticate_user!, only: [:assign_related_issues]
 
-  before_action :authorize_can_resolve_conflicts!, only: [:conflicts, :resolve_conflicts]
+  before_action :authorize_can_resolve_conflicts!, only: [:conflicts, :conflict_for_path, :resolve_conflicts]
 
   def index
     @merge_requests = merge_requests_collection
@@ -170,6 +170,16 @@ class Projects::MergeRequestsController < Projects::ApplicationController
     end
   end
 
+  def conflict_for_path
+    return render_404 unless @merge_request.conflicts_can_be_resolved_in_ui?
+
+    file = @merge_request.conflicts.file_for_path(params[:old_path], params[:new_path])
+
+    return render_404 unless file
+
+    render json: file, full_content: true
+  end
+
   def resolve_conflicts
     return render_404 unless @merge_request.conflicts_can_be_resolved_in_ui?
 
@@ -184,7 +194,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController
       flash[:notice] = 'All merge conflicts were resolved. The merge request can now be merged.'
 
       render json: { redirect_to: namespace_project_merge_request_url(@project.namespace, @project, @merge_request, resolved_conflicts: true) }
-    rescue Gitlab::Conflict::File::MissingResolution => e
+    rescue Gitlab::Conflict::ResolutionError => e
       render status: :bad_request, json: { message: e.message }
     end
   end
diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb
index 5604296cba5c579cab89a9c0efcd705902879ba3..941b01c29e0697027fbccc8e0d72eced440d5819 100644
--- a/app/models/ci/pipeline.rb
+++ b/app/models/ci/pipeline.rb
@@ -49,6 +49,10 @@ module Ci
         transition any => :canceled
       end
 
+      # IMPORTANT
+      # Do not add any operations to this state_machine
+      # Create a separate worker for each new operation
+
       before_transition [:created, :pending] => :running do |pipeline|
         pipeline.started_at = Time.now
       end
@@ -62,13 +66,11 @@ module Ci
       end
 
       after_transition [:created, :pending] => :running do |pipeline|
-        MergeRequest::Metrics.where(merge_request_id: pipeline.merge_requests.map(&:id)).
-          update_all(latest_build_started_at: pipeline.started_at, latest_build_finished_at: nil)
+        pipeline.run_after_commit { PipelineMetricsWorker.perform_async(id) }
       end
 
       after_transition any => [:success] do |pipeline|
-        MergeRequest::Metrics.where(merge_request_id: pipeline.merge_requests.map(&:id)).
-          update_all(latest_build_finished_at: pipeline.finished_at)
+        pipeline.run_after_commit { PipelineMetricsWorker.perform_async(id) }
       end
 
       after_transition [:created, :pending, :running] => :success do |pipeline|
diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb
index 5ccfe11a2a29a12e954791a9677febe71074c969..8c6905a442de644369cab82bc85961fd9977200e 100644
--- a/app/models/merge_request.rb
+++ b/app/models/merge_request.rb
@@ -871,7 +871,7 @@ class MergeRequest < ActiveRecord::Base
       # files.
       conflicts.files.each(&:lines)
       @conflicts_can_be_resolved_in_ui = conflicts.files.length > 0
-    rescue Rugged::OdbError, Gitlab::Conflict::Parser::ParserError, Gitlab::Conflict::FileCollection::ConflictSideMissing
+    rescue Rugged::OdbError, Gitlab::Conflict::Parser::UnresolvableError, Gitlab::Conflict::FileCollection::ConflictSideMissing
       @conflicts_can_be_resolved_in_ui = false
     end
   end
diff --git a/app/services/merge_requests/resolve_service.rb b/app/services/merge_requests/resolve_service.rb
index 19caa038c4415f3198b565e367173f5bd9832173..d22a1d3e0ad3d2c2f797080c41368d4d70074da9 100644
--- a/app/services/merge_requests/resolve_service.rb
+++ b/app/services/merge_requests/resolve_service.rb
@@ -1,5 +1,8 @@
 module MergeRequests
   class ResolveService < MergeRequests::BaseService
+    class MissingFiles < Gitlab::Conflict::ResolutionError
+    end
+
     attr_accessor :conflicts, :rugged, :merge_index, :merge_request
 
     def execute(merge_request)
@@ -10,8 +13,16 @@ module MergeRequests
 
       fetch_their_commit!
 
-      conflicts.files.each do |file|
-        write_resolved_file_to_index(file, params[:sections])
+      params[:files].each do |file_params|
+        conflict_file = merge_request.conflicts.file_for_path(file_params[:old_path], file_params[:new_path])
+
+        write_resolved_file_to_index(conflict_file, file_params)
+      end
+
+      unless merge_index.conflicts.empty?
+        missing_files = merge_index.conflicts.map { |file| file[:ours][:path] }
+
+        raise MissingFiles, "Missing resolutions for the following files: #{missing_files.join(', ')}"
       end
 
       commit_params = {
@@ -23,8 +34,13 @@ module MergeRequests
       project.repository.resolve_conflicts(current_user, merge_request.source_branch, commit_params)
     end
 
-    def write_resolved_file_to_index(file, resolutions)
-      new_file = file.resolve_lines(resolutions).map(&:text).join("\n")
+    def write_resolved_file_to_index(file, params)
+      new_file = if params[:sections]
+                   file.resolve_lines(params[:sections]).map(&:text).join("\n")
+                 elsif params[:content]
+                   file.resolve_content(params[:content])
+                 end
+
       our_path = file.our_path
 
       merge_index.add(path: our_path, oid: rugged.write(new_file, :blob), mode: file.our_mode)
diff --git a/app/views/projects/buttons/_download.html.haml b/app/views/projects/buttons/_download.html.haml
index 9089586a89d02a7b5d763186837b954ba889a4b8..7e83a88913ad3d42ec16a32916d95f59673bc07d 100644
--- a/app/views/projects/buttons/_download.html.haml
+++ b/app/views/projects/buttons/_download.html.haml
@@ -1,5 +1,5 @@
 - if !project.empty_repo? && can?(current_user, :download_code, project)
-  %span{class: 'hidden-xs hidden-sm'}
+  %span{class: 'hidden-xs hidden-sm download-button'}
     .dropdown.inline
       %button.btn{ 'data-toggle' => 'dropdown' }
         = icon('download')
diff --git a/app/views/projects/merge_requests/conflicts.html.haml b/app/views/projects/merge_requests/conflicts.html.haml
index a524936f73cb3179a97516ef01dfb006e7d7991e..d9f74d2cbfbd4c83af628b4c9e418bf9b2de1db2 100644
--- a/app/views/projects/merge_requests/conflicts.html.haml
+++ b/app/views/projects/merge_requests/conflicts.html.haml
@@ -1,11 +1,7 @@
-- class_bindings = "{ |
-    'head': line.isHead, |
-    'origin': line.isOrigin, |
-    'match': line.hasMatch, |
-    'selected': line.isSelected, |
-    'unselected': line.isUnselected }"
-
 - page_title "Merge Conflicts", "#{@merge_request.title} (#{@merge_request.to_reference}", "Merge Requests"
+- content_for :page_specific_javascripts do
+  = page_specific_javascript_tag('merge_conflicts/merge_conflicts_bundle.js')
+  = page_specific_javascript_tag('lib/ace.js')
 = render "projects/merge_requests/show/mr_title"
 
 .merge-request-details.issuable-details
@@ -24,6 +20,21 @@
   = render partial: "projects/merge_requests/conflicts/commit_stats"
 
   .files-wrapper{"v-if" => "!isLoading && !hasError"}
-    = render partial: "projects/merge_requests/conflicts/parallel_view", locals: { class_bindings: class_bindings }
-    = render partial: "projects/merge_requests/conflicts/inline_view", locals: { class_bindings: class_bindings }
+    .files
+      .diff-file.file-holder.conflict{"v-for" => "file in conflictsData.files"}
+        .file-title
+          %i.fa.fa-fw{":class" => "file.iconClass"}
+          %strong {{file.filePath}}
+          = render partial: 'projects/merge_requests/conflicts/file_actions'
+        .diff-content.diff-wrap-lines
+          .diff-wrap-lines.code.file-content.js-syntax-highlight{"v-show" => "!isParallel && file.resolveMode === 'interactive' && file.type === 'text'" }
+            = render partial: "projects/merge_requests/conflicts/components/inline_conflict_lines"
+          .diff-wrap-lines.code.file-content.js-syntax-highlight{"v-show" => "isParallel && file.resolveMode === 'interactive' && file.type === 'text'" }
+            = render partial: "projects/merge_requests/conflicts/components/parallel_conflict_lines"
+          %div{"v-show" => "file.resolveMode === 'edit' ||  file.type === 'text-editor'"}
+            = render partial: "projects/merge_requests/conflicts/components/diff_file_editor"
+
     = render partial: "projects/merge_requests/conflicts/submit_form"
+
+-# Components
+= render partial: 'projects/merge_requests/conflicts/components/parallel_conflict_line'
diff --git a/app/views/projects/merge_requests/conflicts/_commit_stats.html.haml b/app/views/projects/merge_requests/conflicts/_commit_stats.html.haml
index 457c467fba9825e161cccbad4fd94686b241eb7c..5ab3cd96163c3d623fe704ae406c616a813d3fdb 100644
--- a/app/views/projects/merge_requests/conflicts/_commit_stats.html.haml
+++ b/app/views/projects/merge_requests/conflicts/_commit_stats.html.haml
@@ -1,20 +1,16 @@
 .content-block.oneline-block.files-changed{"v-if" => "!isLoading && !hasError"}
-  .inline-parallel-buttons
+  .inline-parallel-buttons{"v-if" => "showDiffViewTypeSwitcher"}
     .btn-group
-      %a.btn{ |
-        ":class" => "{'active': !isParallel}", |
-        "@click" => "handleViewTypeChange('inline')"}
+      %button.btn{":class" => "{'active': !isParallel}", "@click" => "handleViewTypeChange('inline')"}
         Inline
-      %a.btn{ |
-        ":class" => "{'active': isParallel}", |
-        "@click" => "handleViewTypeChange('parallel')"}
+      %button.btn{":class" => "{'active': isParallel}", "@click" => "handleViewTypeChange('parallel')"}
         Side-by-side
 
   .js-toggle-container
     .commit-stat-summary
       Showing
-      %strong.cred {{conflictsCount}} {{conflictsData.conflictsText}}
+      %strong.cred {{conflictsCountText}}
       between
-      %strong {{conflictsData.source_branch}}
+      %strong {{conflictsData.sourceBranch}}
       and
-      %strong {{conflictsData.target_branch}}
+      %strong {{conflictsData.targetBranch}}
diff --git a/app/views/projects/merge_requests/conflicts/_file_actions.html.haml b/app/views/projects/merge_requests/conflicts/_file_actions.html.haml
new file mode 100644
index 0000000000000000000000000000000000000000..05af57acf038bfedf4a491024c78fe581218ad6b
--- /dev/null
+++ b/app/views/projects/merge_requests/conflicts/_file_actions.html.haml
@@ -0,0 +1,12 @@
+.file-actions
+  .btn-group{"v-if" => "file.type === 'text'"}
+    %button.btn{ ":class" => "{ 'active': file.resolveMode == 'interactive' }",
+      '@click' => "onClickResolveModeButton(file, 'interactive')",
+      type: 'button' }
+      Interactive mode
+    %button.btn{ ':class' => "{ 'active': file.resolveMode == 'edit' }",
+      '@click' => "onClickResolveModeButton(file, 'edit')",
+      type: 'button' }
+      Edit inline
+  %a.btn.view-file.btn-file-option{":href" => "file.blobPath"}
+    View file @{{conflictsData.shortCommitSha}}
diff --git a/app/views/projects/merge_requests/conflicts/_inline_view.html.haml b/app/views/projects/merge_requests/conflicts/_inline_view.html.haml
deleted file mode 100644
index 19c7da4b5e39a6a9c1ccbfd2151b76e88ef5e286..0000000000000000000000000000000000000000
--- a/app/views/projects/merge_requests/conflicts/_inline_view.html.haml
+++ /dev/null
@@ -1,28 +0,0 @@
-.files{"v-show" => "!isParallel"}
-  .diff-file.file-holder.conflict.inline-view{"v-for" => "file in conflictsData.files"}
-    .file-title
-      %i.fa.fa-fw{":class" => "file.iconClass"}
-      %strong {{file.filePath}}
-      .file-actions
-        %a.btn.view-file.btn-file-option{":href" => "file.blobPath"}
-          View file @{{conflictsData.shortCommitSha}}
-
-    .diff-content.diff-wrap-lines
-      .diff-wrap-lines.code.file-content.js-syntax-highlight
-        %table
-          %tr.line_holder.diff-inline{"v-for" => "line in file.inlineLines"}
-            %template{"v-if" => "!line.isHeader"}
-              %td.diff-line-num.new_line{":class" => class_bindings}
-                %a {{line.new_line}}
-              %td.diff-line-num.old_line{":class" => class_bindings}
-                %a {{line.old_line}}
-              %td.line_content{":class" => class_bindings}
-                {{{line.richText}}}
-
-            %template{"v-if" => "line.isHeader"}
-              %td.diff-line-num.header{":class" => class_bindings}
-              %td.diff-line-num.header{":class" => class_bindings}
-              %td.line_content.header{":class" => class_bindings}
-                %strong {{{line.richText}}}
-                %button.btn{"@click" => "handleSelected(line.id, line.section)"}
-                  {{line.buttonTitle}}
diff --git a/app/views/projects/merge_requests/conflicts/_parallel_view.html.haml b/app/views/projects/merge_requests/conflicts/_parallel_view.html.haml
deleted file mode 100644
index 2e6f67c2eaf867c0d5aa94e255fd0dfb5458c1e1..0000000000000000000000000000000000000000
--- a/app/views/projects/merge_requests/conflicts/_parallel_view.html.haml
+++ /dev/null
@@ -1,27 +0,0 @@
-.files{"v-show" => "isParallel"}
-  .diff-file.file-holder.conflict.parallel-view{"v-for" => "file in conflictsData.files"}
-    .file-title
-      %i.fa.fa-fw{":class" => "file.iconClass"}
-      %strong {{file.filePath}}
-      .file-actions
-        %a.btn.view-file.btn-file-option{":href" => "file.blobPath"}
-          View file @{{conflictsData.shortCommitSha}}
-
-    .diff-content.diff-wrap-lines
-      .diff-wrap-lines.code.file-content.js-syntax-highlight
-        %table
-          %tr.line_holder.parallel{"v-for" => "section in file.parallelLines"}
-            %template{"v-for" => "line in section"}
-
-              %template{"v-if" => "line.isHeader"}
-                %td.diff-line-num.header{":class" => class_bindings}
-                %td.line_content.header{":class" => class_bindings}
-                  %strong {{line.richText}}
-                  %button.btn{"@click" => "handleSelected(line.id, line.section)"}
-                    {{line.buttonTitle}}
-
-              %template{"v-if" => "!line.isHeader"}
-                %td.diff-line-num.old_line{":class" => class_bindings}
-                  {{line.lineNumber}}
-                %td.line_content.parallel{":class" => class_bindings}
-                  {{{line.richText}}}
diff --git a/app/views/projects/merge_requests/conflicts/_submit_form.html.haml b/app/views/projects/merge_requests/conflicts/_submit_form.html.haml
index 78bd4133ea292416bf3b683b3bce89220be47b28..6ffaa9ad4d226dce4c99c3722fec9cb1a74a717b 100644
--- a/app/views/projects/merge_requests/conflicts/_submit_form.html.haml
+++ b/app/views/projects/merge_requests/conflicts/_submit_form.html.haml
@@ -1,15 +1,16 @@
-.content-block.oneline-block.files-changed
-  %strong.resolved-count {{resolvedCount}}
-  of
-  %strong.total-count {{conflictsCount}}
-  conflicts have been resolved
-
-  .commit-message-container.form-group
-    .max-width-marker
-    %textarea.form-control.js-commit-message{"v-model" => "conflictsData.commitMessage"}
-      {{{conflictsData.commitMessage}}}
-
-  %button{type: "button", class: "btn btn-success js-submit-button", ":disabled" => "!readyToCommit", "@click" => "commit()"}
-    %span {{commitButtonText}}
-
-  = link_to "Cancel", namespace_project_merge_request_path(@merge_request.project.namespace, @merge_request.project, @merge_request), class: "btn btn-cancel"
+.form-horizontal.resolve-conflicts-form
+  .form-group
+    %label.col-sm-2.control-label{ "for" => "commit-message" }
+      Commit message
+    .col-sm-10
+      .commit-message-container
+        .max-width-marker
+        %textarea.form-control.js-commit-message#commit-message{ "v-model" => "conflictsData.commitMessage", "rows" => "5" }
+  .form-group
+    .col-sm-offset-2.col-sm-10
+      .row
+        .col-xs-6
+          %button{ type: "button", class: "btn btn-success js-submit-button", "@click" => "commit()", ":disabled" => "!readyToCommit" }
+            %span {{commitButtonText}}
+        .col-xs-6.text-right
+          = link_to "Cancel", namespace_project_merge_request_path(@merge_request.project.namespace, @merge_request.project, @merge_request), class: "btn btn-cancel"
diff --git a/app/views/projects/merge_requests/conflicts/components/_diff_file_editor.html.haml b/app/views/projects/merge_requests/conflicts/components/_diff_file_editor.html.haml
new file mode 100644
index 0000000000000000000000000000000000000000..3c927d362c28612641a7cc4bda453544b861dde2
--- /dev/null
+++ b/app/views/projects/merge_requests/conflicts/components/_diff_file_editor.html.haml
@@ -0,0 +1,13 @@
+%diff-file-editor{"inline-template" => "true", ":file" => "file", ":on-cancel-discard-confirmation" => "cancelDiscardConfirmation", ":on-accept-discard-confirmation" => "acceptDiscardConfirmation"}
+  .diff-editor-wrap{ "v-show" => "file.showEditor" }
+    .discard-changes-alert-wrap{ "v-if" => "file.promptDiscardConfirmation" }
+      .discard-changes-alert
+        Are you sure you want to discard your changes?
+        .discard-actions
+          %button.btn.btn-sm.btn-close{ "@click" => "acceptDiscardConfirmation(file)" } Discard changes
+          %button.btn.btn-sm{ "@click" => "cancelDiscardConfirmation(file)" } Cancel
+    .editor-wrap{ ":class" => "classObject" }
+      .loading
+        %i.fa.fa-spinner.fa-spin
+      .editor
+        %pre{ "style" => "height: 350px" }
diff --git a/app/views/projects/merge_requests/conflicts/components/_inline_conflict_lines.html.haml b/app/views/projects/merge_requests/conflicts/components/_inline_conflict_lines.html.haml
new file mode 100644
index 0000000000000000000000000000000000000000..f094df7fcaa921e182f09e60771ad447f1c18263
--- /dev/null
+++ b/app/views/projects/merge_requests/conflicts/components/_inline_conflict_lines.html.haml
@@ -0,0 +1,15 @@
+%inline-conflict-lines{ "inline-template" => "true", ":file" => "file"}
+  %table
+    %tr.line_holder.diff-inline{"v-for" => "line in file.inlineLines"}
+      %td.diff-line-num.new_line{":class" => "lineCssClass(line)", "v-if" => "!line.isHeader"}
+        %a {{line.new_line}}
+      %td.diff-line-num.old_line{":class" => "lineCssClass(line)", "v-if" => "!line.isHeader"}
+        %a {{line.old_line}}
+      %td.line_content{":class" => "lineCssClass(line)", "v-if" => "!line.isHeader"}
+        {{{line.richText}}}
+      %td.diff-line-num.header{":class" => "lineCssClass(line)", "v-if" => "line.isHeader"}
+      %td.diff-line-num.header{":class" => "lineCssClass(line)", "v-if" => "line.isHeader"}
+      %td.line_content.header{":class" => "lineCssClass(line)", "v-if" => "line.isHeader"}
+        %strong {{{line.richText}}}
+        %button.btn{ "@click" => "handleSelected(file, line.id, line.section)" }
+          {{line.buttonTitle}}
diff --git a/app/views/projects/merge_requests/conflicts/components/_parallel_conflict_line.html.haml b/app/views/projects/merge_requests/conflicts/components/_parallel_conflict_line.html.haml
new file mode 100644
index 0000000000000000000000000000000000000000..5690bf7419cc1f8899735c2c81aa596d29668e85
--- /dev/null
+++ b/app/views/projects/merge_requests/conflicts/components/_parallel_conflict_line.html.haml
@@ -0,0 +1,10 @@
+%script{"id" => 'parallel-conflict-line', "type" => "text/x-template"}
+  %td.diff-line-num.header{":class" => "lineCssClass(line)", "v-if" => "line.isHeader"}
+  %td.line_content.header{":class" => "lineCssClass(line)", "v-if" => "line.isHeader"}
+    %strong {{line.richText}}
+    %button.btn{"@click" => "handleSelected(file, line.id, line.section)"}
+      {{line.buttonTitle}}
+  %td.diff-line-num.old_line{":class" => "lineCssClass(line)", "v-if" => "!line.isHeader"}
+    {{line.lineNumber}}
+  %td.line_content.parallel{":class" => "lineCssClass(line)", "v-if" => "!line.isHeader"}
+    {{{line.richText}}}
diff --git a/app/views/projects/merge_requests/conflicts/components/_parallel_conflict_lines.html.haml b/app/views/projects/merge_requests/conflicts/components/_parallel_conflict_lines.html.haml
new file mode 100644
index 0000000000000000000000000000000000000000..a8ecdf593934dd7af4c3ec2b346e8656bd832e19
--- /dev/null
+++ b/app/views/projects/merge_requests/conflicts/components/_parallel_conflict_lines.html.haml
@@ -0,0 +1,4 @@
+%parallel-conflict-lines{"inline-template" => "true", ":file" => "file"}
+  %table
+    %tr.line_holder.parallel{"v-for" => "section in file.parallelLines"}
+      %td{"is"=>"parallel-conflict-line", "v-for" => "line in section", ":line" => "line", ":file" => "file"}
diff --git a/app/views/shared/issuable/_sidebar.html.haml b/app/views/shared/issuable/_sidebar.html.haml
index f805998803888c27c639424ef5a4fa465a083ac7..ba9f0c276612eb03d94962d629c68c5ec9e4ee8d 100644
--- a/app/views/shared/issuable/_sidebar.html.haml
+++ b/app/views/shared/issuable/_sidebar.html.haml
@@ -171,5 +171,5 @@
       new LabelsSelect();
       new IssuableContext('#{escape_javascript(current_user.to_json(only: [:username, :id, :name]))}');
       new Subscription('.subscription')
-      new DueDateSelect();
+      new gl.DueDateSelectors();
       sidebar = new Sidebar();
diff --git a/app/workers/pipeline_metrics_worker.rb b/app/workers/pipeline_metrics_worker.rb
new file mode 100644
index 0000000000000000000000000000000000000000..7bb92df3bbd49738891ea3f27e5693cb7040e1a6
--- /dev/null
+++ b/app/workers/pipeline_metrics_worker.rb
@@ -0,0 +1,30 @@
+class PipelineMetricsWorker
+  include Sidekiq::Worker
+
+  sidekiq_options queue: :default
+
+  def perform(pipeline_id)
+    Ci::Pipeline.find_by(id: pipeline_id).try do |pipeline|
+      update_metrics_for_active_pipeline(pipeline) if pipeline.active?
+      update_metrics_for_succeeded_pipeline(pipeline) if pipeline.success?
+    end
+  end
+
+  private
+
+  def update_metrics_for_active_pipeline(pipeline)
+    metrics(pipeline).update_all(latest_build_started_at: pipeline.started_at, latest_build_finished_at: nil)
+  end
+
+  def update_metrics_for_succeeded_pipeline(pipeline)
+    metrics(pipeline).update_all(latest_build_started_at: pipeline.started_at, latest_build_finished_at: pipeline.finished_at)
+  end
+
+  def metrics(pipeline)
+    MergeRequest::Metrics.where(merge_request_id: merge_requests(pipeline))
+  end
+
+  def merge_requests(pipeline)
+    pipeline.merge_requests.map(&:id)
+  end
+end
diff --git a/config/application.rb b/config/application.rb
index 962ffe0708d0de97096b632a02ffed50f0e32fe2..8a9c539cb4375774057ec431908e239c0f27b041 100644
--- a/config/application.rb
+++ b/config/application.rb
@@ -89,6 +89,7 @@ module Gitlab
     config.assets.precompile << "profile/profile_bundle.js"
     config.assets.precompile << "diff_notes/diff_notes_bundle.js"
     config.assets.precompile << "boards/boards_bundle.js"
+    config.assets.precompile << "merge_conflicts/merge_conflicts_bundle.js"
     config.assets.precompile << "boards/test_utils/simulate_drag.js"
     config.assets.precompile << "blob_edit/blob_edit_bundle.js"
     config.assets.precompile << "snippet/snippet_bundle.js"
diff --git a/config/initializers/metrics.rb b/config/initializers/metrics.rb
index be22085b0df75c54e43b09ebea328a7cce4c85af..3b8771543e4fb5f1a1500cb1071924aca5f8388b 100644
--- a/config/initializers/metrics.rb
+++ b/config/initializers/metrics.rb
@@ -67,6 +67,7 @@ if Gitlab::Metrics.enabled?
       ['app', 'finders']                    => ['app', 'finders'],
       ['app', 'mailers', 'emails']          => ['app', 'mailers'],
       ['app', 'services', '**']             => ['app', 'services'],
+      ['lib', 'gitlab', 'conflicts']        => ['lib'],
       ['lib', 'gitlab', 'diff']             => ['lib'],
       ['lib', 'gitlab', 'email', 'message'] => ['lib'],
       ['lib', 'gitlab', 'checks']           => ['lib']
diff --git a/config/routes/project.rb b/config/routes/project.rb
index 2cd8c60794a1a03c2ec2927f29ca1b4b51e305a9..711a59df74424d66ce37e331a1b30b8ec294d0d0 100644
--- a/config/routes/project.rb
+++ b/config/routes/project.rb
@@ -267,6 +267,7 @@ resources :namespaces, path: '/', constraints: { id: /[a-zA-Z.0-9_\-]+/ }, only:
           get :commits
           get :diffs
           get :conflicts
+          get :conflict_for_path
           get :builds
           get :pipelines
           get :merge_check
diff --git a/config/routes/user.rb b/config/routes/user.rb
index dfb5d2a2ba4d4cae868493f92200c6bfed2d701a..0a9c924863da8d86f6e46bd5cc95146856bd8e6c 100644
--- a/config/routes/user.rb
+++ b/config/routes/user.rb
@@ -1,8 +1,5 @@
 require 'constraints/user_url_constrainer'
 
-get '/u/:username', to: redirect('/%{username}'),
-                    constraints: { username: /[a-zA-Z.0-9_\-]+(?<!\.atom)/ }
-
 devise_for :users, controllers: { omniauth_callbacks: :omniauth_callbacks,
                                   registrations: :registrations,
                                   passwords: :passwords,
@@ -23,7 +20,7 @@ constraints(UserUrlConstrainer.new) do
   end
 end
 
-scope(path: 'u/:username',
+scope(path: 'users/:username',
       as: :user,
       constraints: { username: /[a-zA-Z.0-9_\-]+(?<!\.atom)/ },
       controller: :users) do
@@ -36,3 +33,12 @@ scope(path: 'u/:username',
   get :exists
   get '/', to: redirect('/%{username}')
 end
+
+# Compatibility with old routing
+# TODO (dzaporozhets): remove in 10.0
+get '/u/:username', to: redirect('/%{username}'), constraints: { username: /[a-zA-Z.0-9_\-]+(?<!\.atom)/ }
+# TODO (dzaporozhets): remove in 9.0
+get '/u/:username/groups', to: redirect('/users/%{username}/groups'), constraints: { username: /[a-zA-Z.0-9_\-]+/ }
+get '/u/:username/projects', to: redirect('/users/%{username}/projects'), constraints: { username: /[a-zA-Z.0-9_\-]+/ }
+get '/u/:username/snippets', to: redirect('/users/%{username}/snippets'), constraints: { username: /[a-zA-Z.0-9_\-]+/ }
+get '/u/:username/contributed', to: redirect('/users/%{username}/contributed'), constraints: { username: /[a-zA-Z.0-9_\-]+/ }
diff --git a/doc/api/award_emoji.md b/doc/api/award_emoji.md
index c464e3f3f71261c622ca1d7908e29850dffc3406..06111f4ab671a4d73c7f6878500b4395a2793653 100644
--- a/doc/api/award_emoji.md
+++ b/doc/api/award_emoji.md
@@ -43,7 +43,7 @@ Example Response:
       "id": 1,
       "state": "active",
       "avatar_url": "http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon",
-      "web_url": "http://gitlab.example.com/u/root"
+      "web_url": "http://gitlab.example.com/root"
     },
     "created_at": "2016-06-15T10:09:34.206Z",
     "updated_at": "2016-06-15T10:09:34.206Z",
@@ -59,7 +59,7 @@ Example Response:
       "id": 26,
       "state": "active",
       "avatar_url": "http://www.gravatar.com/avatar/7e65550957227bd38fe2d7fbc6fd2f7b?s=80&d=identicon",
-      "web_url": "http://gitlab.example.com/u/user4"
+      "web_url": "http://gitlab.example.com/user4"
     },
     "created_at": "2016-06-15T10:09:34.177Z",
     "updated_at": "2016-06-15T10:09:34.177Z",
@@ -103,7 +103,7 @@ Example Response:
     "id": 26,
     "state": "active",
     "avatar_url": "http://www.gravatar.com/avatar/7e65550957227bd38fe2d7fbc6fd2f7b?s=80&d=identicon",
-    "web_url": "http://gitlab.example.com/u/user4"
+    "web_url": "http://gitlab.example.com/user4"
   },
   "created_at": "2016-06-15T10:09:34.177Z",
   "updated_at": "2016-06-15T10:09:34.177Z",
@@ -146,7 +146,7 @@ Example Response:
     "id": 1,
     "state": "active",
     "avatar_url": "http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon",
-    "web_url": "http://gitlab.example.com/u/root"
+    "web_url": "http://gitlab.example.com/root"
   },
   "created_at": "2016-06-17T17:47:29.266Z",
   "updated_at": "2016-06-17T17:47:29.266Z",
@@ -190,7 +190,7 @@ Example Response:
     "id": 1,
     "state": "active",
     "avatar_url": "http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon",
-    "web_url": "http://gitlab.example.com/u/root"
+    "web_url": "http://gitlab.example.com/root"
   },
   "created_at": "2016-06-17T17:47:29.266Z",
   "updated_at": "2016-06-17T17:47:29.266Z",
@@ -238,7 +238,7 @@ Example Response:
       "id": 26,
       "state": "active",
       "avatar_url": "http://www.gravatar.com/avatar/7e65550957227bd38fe2d7fbc6fd2f7b?s=80&d=identicon",
-      "web_url": "http://gitlab.example.com/u/user4"
+      "web_url": "http://gitlab.example.com/user4"
     },
     "created_at": "2016-06-15T10:09:34.197Z",
     "updated_at": "2016-06-15T10:09:34.197Z",
@@ -279,7 +279,7 @@ Example Response:
     "id": 26,
     "state": "active",
     "avatar_url": "http://www.gravatar.com/avatar/7e65550957227bd38fe2d7fbc6fd2f7b?s=80&d=identicon",
-    "web_url": "http://gitlab.example.com/u/user4"
+    "web_url": "http://gitlab.example.com/user4"
   },
   "created_at": "2016-06-15T10:09:34.197Z",
   "updated_at": "2016-06-15T10:09:34.197Z",
@@ -319,7 +319,7 @@ Example Response:
     "id": 1,
     "state": "active",
     "avatar_url": "http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon",
-    "web_url": "http://gitlab.example.com/u/root"
+    "web_url": "http://gitlab.example.com/root"
   },
   "created_at": "2016-06-17T19:59:55.888Z",
   "updated_at": "2016-06-17T19:59:55.888Z",
@@ -362,7 +362,7 @@ Example Response:
     "id": 1,
     "state": "active",
     "avatar_url": "http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon",
-    "web_url": "http://gitlab.example.com/u/root"
+    "web_url": "http://gitlab.example.com/root"
   },
   "created_at": "2016-06-17T19:59:55.888Z",
   "updated_at": "2016-06-17T19:59:55.888Z",
diff --git a/doc/api/builds.md b/doc/api/builds.md
index e8a9e4743d39e7de64511c8dafa6ded7c1d0e3b4..e40f198696daaeed832c7042d97fdf0bf7d9d9c4 100644
--- a/doc/api/builds.md
+++ b/doc/api/builds.md
@@ -64,7 +64,7 @@ Example of response
       "state": "active",
       "twitter": "",
       "username": "root",
-      "web_url": "http://gitlab.dev/u/root",
+      "web_url": "http://gitlab.dev/root",
       "website_url": ""
     }
   },
@@ -108,7 +108,7 @@ Example of response
       "state": "active",
       "twitter": "",
       "username": "root",
-      "web_url": "http://gitlab.dev/u/root",
+      "web_url": "http://gitlab.dev/root",
       "website_url": ""
     }
   }
@@ -212,7 +212,7 @@ Example of response
       "state": "active",
       "twitter": "",
       "username": "root",
-      "web_url": "http://gitlab.dev/u/root",
+      "web_url": "http://gitlab.dev/root",
       "website_url": ""
     }
   }
@@ -279,7 +279,7 @@ Example of response
     "state": "active",
     "twitter": "",
     "username": "root",
-    "web_url": "http://gitlab.dev/u/root",
+    "web_url": "http://gitlab.dev/root",
     "website_url": ""
   }
 }
diff --git a/doc/api/commits.md b/doc/api/commits.md
index 3e20beefb8a1872eaa3dd50eb5918a5fb54eb35a..6e0882a94dea7ab5793230b75cfe28d8e3d28c9b 100644
--- a/doc/api/commits.md
+++ b/doc/api/commits.md
@@ -288,7 +288,7 @@ Example response:
 ```json
 {
    "author" : {
-      "web_url" : "https://gitlab.example.com/u/thedude",
+      "web_url" : "https://gitlab.example.com/thedude",
       "avatar_url" : "https://gitlab.example.com/uploads/user/avatar/28/The-Big-Lebowski-400-400.png",
       "username" : "thedude",
       "state" : "active",
@@ -343,7 +343,7 @@ Example response:
       "author" : {
          "username" : "thedude",
          "state" : "active",
-         "web_url" : "https://gitlab.example.com/u/thedude",
+         "web_url" : "https://gitlab.example.com/thedude",
          "avatar_url" : "https://gitlab.example.com/uploads/user/avatar/28/The-Big-Lebowski-400-400.png",
          "id" : 28,
          "name" : "Jeff Lebowski"
@@ -370,7 +370,7 @@ Example response:
          "id" : 28,
          "name" : "Jeff Lebowski",
          "username" : "thedude",
-         "web_url" : "https://gitlab.example.com/u/thedude",
+         "web_url" : "https://gitlab.example.com/thedude",
          "state" : "active",
          "avatar_url" : "https://gitlab.example.com/uploads/user/avatar/28/The-Big-Lebowski-400-400.png"
       },
@@ -408,7 +408,7 @@ Example response:
 ```json
 {
    "author" : {
-      "web_url" : "https://gitlab.example.com/u/thedude",
+      "web_url" : "https://gitlab.example.com/thedude",
       "name" : "Jeff Lebowski",
       "avatar_url" : "https://gitlab.example.com/uploads/user/avatar/28/The-Big-Lebowski-400-400.png",
       "username" : "thedude",
diff --git a/doc/api/deployments.md b/doc/api/deployments.md
index 417962de82d42e1d7a0c99523692d1f98d12c222..3d95c4cde604add0089bddf52f8f5bab306ca983 100644
--- a/doc/api/deployments.md
+++ b/doc/api/deployments.md
@@ -56,7 +56,7 @@ Example of response
         "state": "active",
         "twitter": "",
         "username": "root",
-        "web_url": "http://localhost:3000/u/root",
+        "web_url": "http://localhost:3000/root",
         "website_url": ""
       }
     },
@@ -75,7 +75,7 @@ Example of response
       "name": "Administrator",
       "state": "active",
       "username": "root",
-      "web_url": "http://localhost:3000/u/root"
+      "web_url": "http://localhost:3000/root"
     }
   },
   {
@@ -114,7 +114,7 @@ Example of response
         "state": "active",
         "twitter": "",
         "username": "root",
-        "web_url": "http://localhost:3000/u/root",
+        "web_url": "http://localhost:3000/root",
         "website_url": ""
       }
     },
@@ -133,7 +133,7 @@ Example of response
       "name": "Administrator",
       "state": "active",
       "username": "root",
-      "web_url": "http://localhost:3000/u/root"
+      "web_url": "http://localhost:3000/root"
     }
   }
 ]
@@ -169,7 +169,7 @@ Example of response
     "id": 1,
     "state": "active",
     "avatar_url": "http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon",
-    "web_url": "http://localhost:3000/u/root"
+    "web_url": "http://localhost:3000/root"
   },
   "environment": {
     "id": 9,
@@ -193,7 +193,7 @@ Example of response
       "id": 1,
       "state": "active",
       "avatar_url": "http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon",
-      "web_url": "http://localhost:3000/u/root",
+      "web_url": "http://localhost:3000/root",
       "created_at": "2016-08-11T07:09:20.351Z",
       "is_admin": true,
       "bio": null,
diff --git a/doc/api/issues.md b/doc/api/issues.md
index eed0d2fce5172a62aaa983a21847880e34036068..134263d27b4b1187621e9658b843255d5334288b 100644
--- a/doc/api/issues.md
+++ b/doc/api/issues.md
@@ -46,7 +46,7 @@ Example response:
       "author" : {
          "state" : "active",
          "id" : 18,
-         "web_url" : "https://gitlab.example.com/u/eileen.lowe",
+         "web_url" : "https://gitlab.example.com/eileen.lowe",
          "name" : "Alexandra Bashirian",
          "avatar_url" : null,
          "username" : "eileen.lowe"
@@ -67,7 +67,7 @@ Example response:
          "state" : "active",
          "id" : 1,
          "name" : "Administrator",
-         "web_url" : "https://gitlab.example.com/u/root",
+         "web_url" : "https://gitlab.example.com/root",
          "avatar_url" : null,
          "username" : "root"
       },
@@ -134,7 +134,7 @@ Example response:
       },
       "author" : {
          "state" : "active",
-         "web_url" : "https://gitlab.example.com/u/root",
+         "web_url" : "https://gitlab.example.com/root",
          "avatar_url" : null,
          "username" : "root",
          "id" : 1,
@@ -145,7 +145,7 @@ Example response:
       "iid" : 1,
       "assignee" : {
          "avatar_url" : null,
-         "web_url" : "https://gitlab.example.com/u/lennie",
+         "web_url" : "https://gitlab.example.com/lennie",
          "state" : "active",
          "username" : "lennie",
          "id" : 9,
@@ -215,7 +215,7 @@ Example response:
       },
       "author" : {
          "state" : "active",
-         "web_url" : "https://gitlab.example.com/u/root",
+         "web_url" : "https://gitlab.example.com/root",
          "avatar_url" : null,
          "username" : "root",
          "id" : 1,
@@ -226,7 +226,7 @@ Example response:
       "iid" : 1,
       "assignee" : {
          "avatar_url" : null,
-         "web_url" : "https://gitlab.example.com/u/lennie",
+         "web_url" : "https://gitlab.example.com/lennie",
          "state" : "active",
          "username" : "lennie",
          "id" : 9,
@@ -281,7 +281,7 @@ Example response:
    },
    "author" : {
       "state" : "active",
-      "web_url" : "https://gitlab.example.com/u/root",
+      "web_url" : "https://gitlab.example.com/root",
       "avatar_url" : null,
       "username" : "root",
       "id" : 1,
@@ -292,7 +292,7 @@ Example response:
    "iid" : 1,
    "assignee" : {
       "avatar_url" : null,
-      "web_url" : "https://gitlab.example.com/u/lennie",
+      "web_url" : "https://gitlab.example.com/lennie",
       "state" : "active",
       "username" : "lennie",
       "id" : 9,
@@ -357,7 +357,7 @@ Example response:
       "name" : "Alexandra Bashirian",
       "avatar_url" : null,
       "state" : "active",
-      "web_url" : "https://gitlab.example.com/u/eileen.lowe",
+      "web_url" : "https://gitlab.example.com/eileen.lowe",
       "id" : 18,
       "username" : "eileen.lowe"
    },
@@ -414,7 +414,7 @@ Example response:
       "username" : "eileen.lowe",
       "id" : 18,
       "state" : "active",
-      "web_url" : "https://gitlab.example.com/u/eileen.lowe"
+      "web_url" : "https://gitlab.example.com/eileen.lowe"
    },
    "state" : "closed",
    "title" : "Issues with auth",
@@ -500,7 +500,7 @@ Example response:
     "id": 12,
     "state": "active",
     "avatar_url": "http://www.gravatar.com/avatar/46f6f7dc858ada7be1853f7fb96e81da?s=80&d=identicon",
-    "web_url": "https://gitlab.example.com/u/axel.block"
+    "web_url": "https://gitlab.example.com/axel.block"
   },
   "author": {
     "name": "Kris Steuber",
@@ -508,7 +508,7 @@ Example response:
     "id": 10,
     "state": "active",
     "avatar_url": "http://www.gravatar.com/avatar/7a190fecbaa68212a4b68aeb6e3acd10?s=80&d=identicon",
-    "web_url": "https://gitlab.example.com/u/solon.cremin"
+    "web_url": "https://gitlab.example.com/solon.cremin"
   },
   "due_date": null,
   "web_url": "http://example.com/example/example/issues/11",
@@ -557,7 +557,7 @@ Example response:
     "id": 12,
     "state": "active",
     "avatar_url": "http://www.gravatar.com/avatar/46f6f7dc858ada7be1853f7fb96e81da?s=80&d=identicon",
-    "web_url": "https://gitlab.example.com/u/axel.block"
+    "web_url": "https://gitlab.example.com/axel.block"
   },
   "author": {
     "name": "Kris Steuber",
@@ -565,7 +565,7 @@ Example response:
     "id": 10,
     "state": "active",
     "avatar_url": "http://www.gravatar.com/avatar/7a190fecbaa68212a4b68aeb6e3acd10?s=80&d=identicon",
-    "web_url": "https://gitlab.example.com/u/solon.cremin"
+    "web_url": "https://gitlab.example.com/solon.cremin"
   },
   "due_date": null,
   "web_url": "http://example.com/example/example/issues/11",
@@ -614,7 +614,7 @@ Example response:
     "id": 21,
     "state": "active",
     "avatar_url": "http://www.gravatar.com/avatar/3e6f06a86cf27fa8b56f3f74f7615987?s=80&d=identicon",
-    "web_url": "https://gitlab.example.com/u/keyon"
+    "web_url": "https://gitlab.example.com/keyon"
   },
   "author": {
     "name": "Vivian Hermann",
@@ -622,7 +622,7 @@ Example response:
     "id": 11,
     "state": "active",
     "avatar_url": "http://www.gravatar.com/avatar/5224fd70153710e92fb8bcf79ac29d67?s=80&d=identicon",
-    "web_url": "https://gitlab.example.com/u/orville"
+    "web_url": "https://gitlab.example.com/orville"
   },
   "subscribed": false,
   "due_date": null,
@@ -669,7 +669,7 @@ Example response:
     "id": 1,
     "state": "active",
     "avatar_url": "http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon",
-    "web_url": "https://gitlab.example.com/u/root"
+    "web_url": "https://gitlab.example.com/root"
   },
   "action_name": "marked",
   "target_type": "Issue",
@@ -700,7 +700,7 @@ Example response:
       "id": 14,
       "state": "active",
       "avatar_url": "http://www.gravatar.com/avatar/a7fa515d53450023c83d62986d0658a8?s=80&d=identicon",
-      "web_url": "https://gitlab.example.com/u/francisca"
+      "web_url": "https://gitlab.example.com/francisca"
     },
     "author": {
       "name": "Maxie Medhurst",
@@ -708,7 +708,7 @@ Example response:
       "id": 12,
       "state": "active",
       "avatar_url": "http://www.gravatar.com/avatar/a0d477b3ea21970ce6ffcbb817b0b435?s=80&d=identicon",
-      "web_url": "https://gitlab.example.com/u/craig_rutherford"
+      "web_url": "https://gitlab.example.com/craig_rutherford"
     },
     "subscribed": true,
     "user_notes_count": 7,
diff --git a/doc/api/keys.md b/doc/api/keys.md
index faa6f212b433a2ae9e1bb1b3984ff93d14ad2bef..b68f08a007d53dc81ffc82aca1de8aecddd46bbf 100644
--- a/doc/api/keys.md
+++ b/doc/api/keys.md
@@ -24,7 +24,7 @@ Parameters:
     "id": 25,
     "state": "active",
     "avatar_url": "http://www.gravatar.com/avatar/cfa35b8cd2ec278026357769582fa563?s=40\u0026d=identicon",
-    "web_url": "http://localhost:3000/u/john_smith",
+    "web_url": "http://localhost:3000/john_smith",
     "created_at": "2015-09-03T07:24:01.670Z",
     "is_admin": false,
     "bio": null,
diff --git a/doc/api/merge_requests.md b/doc/api/merge_requests.md
index 494040a1ce83e75c5a0f3eb24c31e93103157e3d..f4167403c2cd0d2ce11a69992a999b915d28213b 100644
--- a/doc/api/merge_requests.md
+++ b/doc/api/merge_requests.md
@@ -621,7 +621,7 @@ Example response when the GitLab issue tracker is used:
       "author" : {
          "state" : "active",
          "id" : 18,
-         "web_url" : "https://gitlab.example.com/u/eileen.lowe",
+         "web_url" : "https://gitlab.example.com/eileen.lowe",
          "name" : "Alexandra Bashirian",
          "avatar_url" : null,
          "username" : "eileen.lowe"
@@ -642,7 +642,7 @@ Example response when the GitLab issue tracker is used:
          "state" : "active",
          "id" : 1,
          "name" : "Administrator",
-         "web_url" : "https://gitlab.example.com/u/root",
+         "web_url" : "https://gitlab.example.com/root",
          "avatar_url" : null,
          "username" : "root"
       },
@@ -711,7 +711,7 @@ Example response:
     "id": 19,
     "state": "active",
     "avatar_url": "http://www.gravatar.com/avatar/39ce4a2822cc896933ffbd68c1470e55?s=80&d=identicon",
-    "web_url": "https://gitlab.example.com/u/leila"
+    "web_url": "https://gitlab.example.com/leila"
   },
   "assignee": {
     "name": "Celine Wehner",
@@ -719,7 +719,7 @@ Example response:
     "id": 16,
     "state": "active",
     "avatar_url": "http://www.gravatar.com/avatar/f4cd5605b769dd2ce405a27c6e6f2684?s=80&d=identicon",
-    "web_url": "https://gitlab.example.com/u/carli"
+    "web_url": "https://gitlab.example.com/carli"
   },
   "source_project_id": 5,
   "target_project_id": 5,
@@ -787,7 +787,7 @@ Example response:
     "id": 19,
     "state": "active",
     "avatar_url": "http://www.gravatar.com/avatar/39ce4a2822cc896933ffbd68c1470e55?s=80&d=identicon",
-    "web_url": "https://gitlab.example.com/u/leila"
+    "web_url": "https://gitlab.example.com/leila"
   },
   "assignee": {
     "name": "Celine Wehner",
@@ -795,7 +795,7 @@ Example response:
     "id": 16,
     "state": "active",
     "avatar_url": "http://www.gravatar.com/avatar/f4cd5605b769dd2ce405a27c6e6f2684?s=80&d=identicon",
-    "web_url": "https://gitlab.example.com/u/carli"
+    "web_url": "https://gitlab.example.com/carli"
   },
   "source_project_id": 5,
   "target_project_id": 5,
@@ -858,7 +858,7 @@ Example response:
     "id": 1,
     "state": "active",
     "avatar_url": "http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon",
-    "web_url": "https://gitlab.example.com/u/root"
+    "web_url": "https://gitlab.example.com/root"
   },
   "action_name": "marked",
   "target_type": "MergeRequest",
@@ -881,7 +881,7 @@ Example response:
       "id": 14,
       "state": "active",
       "avatar_url": "http://www.gravatar.com/avatar/a7fa515d53450023c83d62986d0658a8?s=80&d=identicon",
-      "web_url": "https://gitlab.example.com/u/francisca"
+      "web_url": "https://gitlab.example.com/francisca"
     },
     "assignee": {
       "name": "Dr. Gabrielle Strosin",
@@ -889,7 +889,7 @@ Example response:
       "id": 4,
       "state": "active",
       "avatar_url": "http://www.gravatar.com/avatar/733005fcd7e6df12d2d8580171ccb966?s=80&d=identicon",
-      "web_url": "https://gitlab.example.com/u/barrett.krajcik"
+      "web_url": "https://gitlab.example.com/barrett.krajcik"
     },
     "source_project_id": 3,
     "target_project_id": 3,
diff --git a/doc/api/notes.md b/doc/api/notes.md
index 572844b8b3f7c22323c05b447afc34d4bb93f01c..58d40eecf3e3b33390d89e531713f6a3c8e16a05 100644
--- a/doc/api/notes.md
+++ b/doc/api/notes.md
@@ -143,7 +143,7 @@ Example Response:
     "state": "active",
     "created_at": "2013-09-30T13:46:01Z",
     "avatar_url": "http://www.gravatar.com/avatar/5224fd70153710e92fb8bcf79ac29d67?s=80&d=identicon",
-    "web_url": "https://gitlab.example.com/u/pipin"
+    "web_url": "https://gitlab.example.com/pipin"
   },
   "created_at": "2016-04-05T22:10:44.164Z",
   "system": false,
@@ -268,7 +268,7 @@ Example Response:
     "state": "active",
     "created_at": "2013-09-30T13:46:01Z",
     "avatar_url": "http://www.gravatar.com/avatar/5224fd70153710e92fb8bcf79ac29d67?s=80&d=identicon",
-    "web_url": "https://gitlab.example.com/u/pipin"
+    "web_url": "https://gitlab.example.com/pipin"
   },
   "created_at": "2016-04-06T16:51:53.239Z",
   "system": false,
@@ -398,7 +398,7 @@ Example Response:
     "state": "active",
     "created_at": "2013-09-30T13:46:01Z",
     "avatar_url": "http://www.gravatar.com/avatar/5224fd70153710e92fb8bcf79ac29d67?s=80&d=identicon",
-    "web_url": "https://gitlab.example.com/u/pipin"
+    "web_url": "https://gitlab.example.com/pipin"
   },
   "created_at": "2016-04-05T22:11:59.923Z",
   "system": false,
diff --git a/doc/api/pipelines.md b/doc/api/pipelines.md
index 847408a7f617dec115b5f08de5534f6b43427032..a29b3eb6f44527a8de0e206fb5c276cbab5dc917 100644
--- a/doc/api/pipelines.md
+++ b/doc/api/pipelines.md
@@ -34,7 +34,7 @@ Example of response
       "id": 1,
       "state": "active",
       "avatar_url": "http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon",
-      "web_url": "http://localhost:3000/u/root"
+      "web_url": "http://localhost:3000/root"
     },
     "created_at": "2016-08-16T10:23:19.007Z",
     "updated_at": "2016-08-16T10:23:19.216Z",
@@ -57,7 +57,7 @@ Example of response
       "id": 1,
       "state": "active",
       "avatar_url": "http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon",
-      "web_url": "http://localhost:3000/u/root"
+      "web_url": "http://localhost:3000/root"
     },
     "created_at": "2016-08-16T10:23:21.184Z",
     "updated_at": "2016-08-16T10:23:21.314Z",
@@ -103,7 +103,7 @@ Example of response
     "id": 1,
     "state": "active",
     "avatar_url": "http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon",
-    "web_url": "http://localhost:3000/u/root"
+    "web_url": "http://localhost:3000/root"
   },
   "created_at": "2016-08-11T11:28:34.085Z",
   "updated_at": "2016-08-11T11:32:35.169Z",
@@ -148,7 +148,7 @@ Response:
     "id": 1,
     "state": "active",
     "avatar_url": "http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon",
-    "web_url": "http://localhost:3000/u/root"
+    "web_url": "http://localhost:3000/root"
   },
   "created_at": "2016-08-11T11:28:34.085Z",
   "updated_at": "2016-08-11T11:32:35.169Z",
@@ -193,7 +193,7 @@ Response:
     "id": 1,
     "state": "active",
     "avatar_url": "http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon",
-    "web_url": "http://localhost:3000/u/root"
+    "web_url": "http://localhost:3000/root"
   },
   "created_at": "2016-08-11T11:28:34.085Z",
   "updated_at": "2016-08-11T11:32:35.169Z",
diff --git a/doc/api/projects.md b/doc/api/projects.md
index f96bf7f6d63f0277b176b4e43caa229fdeb2f688..b7791b4748a963282e5cab88e9d9e869178b3bbb 100644
--- a/doc/api/projects.md
+++ b/doc/api/projects.md
@@ -465,7 +465,7 @@ Parameters:
       "id": 1,
       "state": "active",
       "avatar_url": "http://localhost:3000/uploads/user/avatar/1/fox_avatar.png",
-      "web_url": "http://localhost:3000/u/root"
+      "web_url": "http://localhost:3000/root"
     },
     "author_username": "root"
   },
@@ -482,7 +482,7 @@ Parameters:
       "id": 1,
       "state": "active",
       "avatar_url": "http://localhost:3000/uploads/user/avatar/1/fox_avatar.png",
-      "web_url": "http://localhost:3000/u/root"
+      "web_url": "http://localhost:3000/root"
     },
     "author_username": "john",
     "data": {
@@ -528,7 +528,7 @@ Parameters:
       "id": 1,
       "state": "active",
       "avatar_url": "http://localhost:3000/uploads/user/avatar/1/fox_avatar.png",
-      "web_url": "http://localhost:3000/u/root"
+      "web_url": "http://localhost:3000/root"
     },
     "author_username": "root"
   },
@@ -552,7 +552,7 @@ Parameters:
         "id": 1,
         "state": "active",
         "avatar_url": "http://localhost:3000/uploads/user/avatar/1/fox_avatar.png",
-        "web_url": "http://localhost:3000/u/root"
+        "web_url": "http://localhost:3000/root"
       },
       "created_at": "2015-12-04T10:33:56.698Z",
       "system": false,
@@ -567,7 +567,7 @@ Parameters:
       "id": 1,
       "state": "active",
       "avatar_url": "http://localhost:3000/uploads/user/avatar/1/fox_avatar.png",
-      "web_url": "http://localhost:3000/u/root"
+      "web_url": "http://localhost:3000/root"
     },
     "author_username": "root"
   }
diff --git a/doc/api/todos.md b/doc/api/todos.md
index 0cd644dfd2fe2bbd4e9e84cab7b990fa2e1fecda..a5e818010247a750a9542f1cf5051477c741611d 100644
--- a/doc/api/todos.md
+++ b/doc/api/todos.md
@@ -44,7 +44,7 @@ Example Response:
       "id": 1,
       "state": "active",
       "avatar_url": "http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon",
-      "web_url": "https://gitlab.example.com/u/root"
+      "web_url": "https://gitlab.example.com/root"
     },
     "action_name": "marked",
     "target_type": "MergeRequest",
@@ -67,7 +67,7 @@ Example Response:
         "id": 12,
         "state": "active",
         "avatar_url": "http://www.gravatar.com/avatar/a0d477b3ea21970ce6ffcbb817b0b435?s=80&d=identicon",
-        "web_url": "https://gitlab.example.com/u/craig_rutherford"
+        "web_url": "https://gitlab.example.com/craig_rutherford"
       },
       "assignee": {
         "name": "Administrator",
@@ -75,7 +75,7 @@ Example Response:
         "id": 1,
         "state": "active",
         "avatar_url": "http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon",
-        "web_url": "https://gitlab.example.com/u/root"
+        "web_url": "https://gitlab.example.com/root"
       },
       "source_project_id": 2,
       "target_project_id": 2,
@@ -117,7 +117,7 @@ Example Response:
       "id": 12,
       "state": "active",
       "avatar_url": "http://www.gravatar.com/avatar/a0d477b3ea21970ce6ffcbb817b0b435?s=80&d=identicon",
-      "web_url": "https://gitlab.example.com/u/craig_rutherford"
+      "web_url": "https://gitlab.example.com/craig_rutherford"
     },
     "action_name": "assigned",
     "target_type": "MergeRequest",
@@ -140,7 +140,7 @@ Example Response:
         "id": 12,
         "state": "active",
         "avatar_url": "http://www.gravatar.com/avatar/a0d477b3ea21970ce6ffcbb817b0b435?s=80&d=identicon",
-        "web_url": "https://gitlab.example.com/u/craig_rutherford"
+        "web_url": "https://gitlab.example.com/craig_rutherford"
       },
       "assignee": {
         "name": "Administrator",
@@ -148,7 +148,7 @@ Example Response:
         "id": 1,
         "state": "active",
         "avatar_url": "http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon",
-        "web_url": "https://gitlab.example.com/u/root"
+        "web_url": "https://gitlab.example.com/root"
       },
       "source_project_id": 2,
       "target_project_id": 2,
@@ -215,7 +215,7 @@ Example Response:
       "id": 1,
       "state": "active",
       "avatar_url": "http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon",
-      "web_url": "https://gitlab.example.com/u/root"
+      "web_url": "https://gitlab.example.com/root"
     },
     "action_name": "marked",
     "target_type": "MergeRequest",
@@ -238,7 +238,7 @@ Example Response:
         "id": 12,
         "state": "active",
         "avatar_url": "http://www.gravatar.com/avatar/a0d477b3ea21970ce6ffcbb817b0b435?s=80&d=identicon",
-        "web_url": "https://gitlab.example.com/u/craig_rutherford"
+        "web_url": "https://gitlab.example.com/craig_rutherford"
       },
       "assignee": {
         "name": "Administrator",
@@ -246,7 +246,7 @@ Example Response:
         "id": 1,
         "state": "active",
         "avatar_url": "http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon",
-        "web_url": "https://gitlab.example.com/u/root"
+        "web_url": "https://gitlab.example.com/root"
       },
       "source_project_id": 2,
       "target_project_id": 2,
diff --git a/doc/api/users.md b/doc/api/users.md
index a52b2d51d78118b07e2ef6daebc474ecacbf70a2..2b12770d5a58b2749e3867b7e73dfb24dfbb46d9 100644
--- a/doc/api/users.md
+++ b/doc/api/users.md
@@ -20,7 +20,7 @@ GET /users
     "name": "John Smith",
     "state": "active",
     "avatar_url": "http://localhost:3000/uploads/user/avatar/1/cd8.jpeg",
-    "web_url": "http://localhost:3000/u/john_smith"
+    "web_url": "http://localhost:3000/john_smith"
   },
   {
     "id": 2,
@@ -28,7 +28,7 @@ GET /users
     "name": "Jack Smith",
     "state": "blocked",
     "avatar_url": "http://gravatar.com/../e32131cd8.jpeg",
-    "web_url": "http://localhost:3000/u/jack_smith"
+    "web_url": "http://localhost:3000/jack_smith"
   }
 ]
 ```
@@ -48,7 +48,7 @@ GET /users
     "name": "John Smith",
     "state": "active",
     "avatar_url": "http://localhost:3000/uploads/user/avatar/1/index.jpg",
-    "web_url": "http://localhost:3000/u/john_smith",
+    "web_url": "http://localhost:3000/john_smith",
     "created_at": "2012-05-23T08:00:58Z",
     "is_admin": false,
     "bio": null,
@@ -81,7 +81,7 @@ GET /users
     "name": "Jack Smith",
     "state": "blocked",
     "avatar_url": "http://localhost:3000/uploads/user/avatar/2/index.jpg",
-    "web_url": "http://localhost:3000/u/jack_smith",
+    "web_url": "http://localhost:3000/jack_smith",
     "created_at": "2012-05-23T08:01:01Z",
     "is_admin": false,
     "bio": null,
@@ -141,7 +141,7 @@ Parameters:
   "name": "John Smith",
   "state": "active",
   "avatar_url": "http://localhost:3000/uploads/user/avatar/1/cd8.jpeg",
-  "web_url": "http://localhost:3000/u/john_smith",
+  "web_url": "http://localhost:3000/john_smith",
   "created_at": "2012-05-23T08:00:58Z",
   "is_admin": false,
   "bio": null,
@@ -172,7 +172,7 @@ Parameters:
   "name": "John Smith",
   "state": "active",
   "avatar_url": "http://localhost:3000/uploads/user/avatar/1/index.jpg",
-  "web_url": "http://localhost:3000/u/john_smith",
+  "web_url": "http://localhost:3000/john_smith",
   "created_at": "2012-05-23T08:00:58Z",
   "is_admin": false,
   "bio": null,
@@ -293,7 +293,7 @@ GET /user
   "name": "John Smith",
   "state": "active",
   "avatar_url": "http://localhost:3000/uploads/user/avatar/1/index.jpg",
-  "web_url": "http://localhost:3000/u/john_smith",
+  "web_url": "http://localhost:3000/john_smith",
   "created_at": "2012-05-23T08:00:58Z",
   "is_admin": false,
   "bio": null,
@@ -665,7 +665,7 @@ Example response:
       "id": 1,
       "state": "active",
       "avatar_url": "http://localhost:3000/uploads/user/avatar/1/fox_avatar.png",
-      "web_url": "http://localhost:3000/u/root"
+      "web_url": "http://localhost:3000/root"
     },
     "author_username": "root"
   },
@@ -682,7 +682,7 @@ Example response:
       "id": 1,
       "state": "active",
       "avatar_url": "http://localhost:3000/uploads/user/avatar/1/fox_avatar.png",
-      "web_url": "http://localhost:3000/u/root"
+      "web_url": "http://localhost:3000/root"
     },
     "author_username": "john",
     "data": {
@@ -728,7 +728,7 @@ Example response:
       "id": 1,
       "state": "active",
       "avatar_url": "http://localhost:3000/uploads/user/avatar/1/fox_avatar.png",
-      "web_url": "http://localhost:3000/u/root"
+      "web_url": "http://localhost:3000/root"
     },
     "author_username": "root"
   },
@@ -752,7 +752,7 @@ Example response:
         "id": 1,
         "state": "active",
         "avatar_url": "http://localhost:3000/uploads/user/avatar/1/fox_avatar.png",
-        "web_url": "http://localhost:3000/u/root"
+        "web_url": "http://localhost:3000/root"
       },
       "created_at": "2015-12-04T10:33:56.698Z",
       "system": false,
@@ -767,7 +767,7 @@ Example response:
       "id": 1,
       "state": "active",
       "avatar_url": "http://localhost:3000/uploads/user/avatar/1/fox_avatar.png",
-      "web_url": "http://localhost:3000/u/root"
+      "web_url": "http://localhost:3000/root"
     },
     "author_username": "root"
   }
diff --git a/doc/development/performance.md b/doc/development/performance.md
index 65d348290250cc1157312c7d5f63a249471a6f6c..c4a964d1da3d84a4a7a91e7457b9d71fe7856fa7 100644
--- a/doc/development/performance.md
+++ b/doc/development/performance.md
@@ -254,5 +254,5 @@ impact on runtime performance, and as such, using a constant instead of
 referencing an object directly may even slow code down.
 
 [#15607]: https://gitlab.com/gitlab-org/gitlab-ce/issues/15607
-[yorickpeterse]: https://gitlab.com/u/yorickpeterse
+[yorickpeterse]: https://gitlab.com/yorickpeterse
 [anti-pattern]: https://en.wikipedia.org/wiki/Anti-pattern
diff --git a/doc/raketasks/backup_hrz.png b/doc/raketasks/backup_hrz.png
index 42084717ebe249e49fd9ac6574ef65f2868bb2ab..287587609a1aee884767e62dd6066847a60f443b 100644
Binary files a/doc/raketasks/backup_hrz.png and b/doc/raketasks/backup_hrz.png differ
diff --git a/features/explore/projects.feature b/features/explore/projects.feature
index 092e18d1b8697f0ad720aa9626e1ef14541c4366..4e0f4486ab7ac26c302e3c6bfdc03202374c792a 100644
--- a/features/explore/projects.feature
+++ b/features/explore/projects.feature
@@ -128,6 +128,7 @@ Feature: Explore Projects
     And project "Archive" has comments
     And I sign in as a user
     And project "Community" has comments
+    And trending projects are refreshed
     When I visit the explore trending projects
     Then I should see project "Community"
     And I should not see project "Internal"
diff --git a/features/steps/project/commits/commits.rb b/features/steps/project/commits/commits.rb
index b08912de25f014cddb06036ce55bddf78ec5cf5f..244306e8464719332d73e833caa35c6584fa74c0 100644
--- a/features/steps/project/commits/commits.rb
+++ b/features/steps/project/commits/commits.rb
@@ -21,7 +21,7 @@ class Spinach::Features::ProjectCommits < Spinach::FeatureSteps
     expect(response_headers['Content-Type']).to have_content("application/atom+xml")
     expect(body).to have_selector("title", text: "#{@project.name}:master commits")
     expect(body).to have_selector("author email", text: commit.author_email)
-    expect(body).to have_selector("entry summary", text: commit.description[0..10])
+    expect(body).to have_selector("entry summary", text: commit.description[0..10].delete("\r"))
   end
 
   step 'I click on tag link' do
diff --git a/features/steps/project/merge_requests.rb b/features/steps/project/merge_requests.rb
index de065dffbc24abca2b9596fe80ca6e3dc00d1981..44346d99f4492401f56a0a34d04834f1443e4a5a 100644
--- a/features/steps/project/merge_requests.rb
+++ b/features/steps/project/merge_requests.rb
@@ -512,6 +512,7 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps
   step 'I should see new target branch changes' do
     expect(page).to have_content 'Request to merge fix into feature'
     expect(page).to have_content 'Target branch changed from merge-test to feature'
+    wait_for_ajax
   end
 
   step 'I click on "Email Patches"' do
diff --git a/features/steps/shared/project.rb b/features/steps/shared/project.rb
index afbd8ef123306da2ff89e4402e98c7ff98dd8cb9..cab85a48396b69465c2e0b2013ff532836529360 100644
--- a/features/steps/shared/project.rb
+++ b/features/steps/shared/project.rb
@@ -218,6 +218,10 @@ module SharedProject
     2.times { create(:note_on_issue, project: project) }
   end
 
+  step 'trending projects are refreshed' do
+    TrendingProject.refresh!
+  end
+
   step 'project "Shop" has labels: "bug", "feature", "enhancement"' do
     project = Project.find_by(name: "Shop")
     create(:label, project: project, title: 'bug')
diff --git a/lib/banzai/filter/set_direction_filter.rb b/lib/banzai/filter/set_direction_filter.rb
new file mode 100644
index 0000000000000000000000000000000000000000..c2976aeb7c6dff663fa95e389722b2ed2a7cdc4f
--- /dev/null
+++ b/lib/banzai/filter/set_direction_filter.rb
@@ -0,0 +1,15 @@
+module Banzai
+  module Filter
+    # HTML filter that sets dir="auto" for RTL languages support
+    class SetDirectionFilter < HTML::Pipeline::Filter
+      def call
+        # select these elements just on top level of the document
+        doc.xpath('p|h1|h2|h3|h4|h5|h6|ol|ul[not(@class="section-nav")]|blockquote|table').each do |el|
+          el['dir'] = 'auto'
+        end
+
+        doc
+      end
+    end
+  end
+end
diff --git a/lib/banzai/pipeline/gfm_pipeline.rb b/lib/banzai/pipeline/gfm_pipeline.rb
index 8d94b199c6680194be3b8ff2b53e37f63de0d865..5da2d0b008c860ad9c27c49506aabcc05ce2c6b4 100644
--- a/lib/banzai/pipeline/gfm_pipeline.rb
+++ b/lib/banzai/pipeline/gfm_pipeline.rb
@@ -25,7 +25,9 @@ module Banzai
           Filter::MilestoneReferenceFilter,
 
           Filter::TaskListFilter,
-          Filter::InlineDiffFilter
+          Filter::InlineDiffFilter,
+
+          Filter::SetDirectionFilter
         ]
       end
 
diff --git a/lib/gitlab/conflict/file.rb b/lib/gitlab/conflict/file.rb
index dff9e29c6a5f8fdf0562c6ab0616405e802de26d..c843315782dc1eb167c4dfecda4f99024c75af1f 100644
--- a/lib/gitlab/conflict/file.rb
+++ b/lib/gitlab/conflict/file.rb
@@ -4,7 +4,7 @@ module Gitlab
       include Gitlab::Routing.url_helpers
       include IconsHelper
 
-      class MissingResolution < StandardError
+      class MissingResolution < ResolutionError
       end
 
       CONTEXT_LINES = 3
@@ -21,12 +21,34 @@ module Gitlab
         @match_line_headers = {}
       end
 
+      def content
+        merge_file_result[:data]
+      end
+
+      def our_blob
+        @our_blob ||= repository.blob_at(merge_request.diff_refs.head_sha, our_path)
+      end
+
+      def type
+        lines unless @type
+
+        @type.inquiry
+      end
+
       # Array of Gitlab::Diff::Line objects
       def lines
-        @lines ||= Gitlab::Conflict::Parser.new.parse(merge_file_result[:data],
+        return @lines if defined?(@lines)
+
+        begin
+          @type = 'text'
+          @lines = Gitlab::Conflict::Parser.new.parse(content,
                                                       our_path: our_path,
                                                       their_path: their_path,
                                                       parent_file: self)
+        rescue Gitlab::Conflict::Parser::ParserError
+          @type = 'text-editor'
+          @lines = nil
+        end
       end
 
       def resolve_lines(resolution)
@@ -53,6 +75,14 @@ module Gitlab
         end.compact
       end
 
+      def resolve_content(resolution)
+        if resolution == content
+          raise MissingResolution, "Resolved content has no changes for file #{our_path}"
+        end
+
+        resolution
+      end
+
       def highlight_lines!
         their_file = lines.reject { |line| line.type == 'new' }.map(&:text).join("\n")
         our_file = lines.reject { |line| line.type == 'old' }.map(&:text).join("\n")
@@ -170,21 +200,39 @@ module Gitlab
         match_line.text = "@@ -#{match_line.old_pos},#{line.old_pos} +#{match_line.new_pos},#{line.new_pos} @@#{header}"
       end
 
-      def as_json(opts = nil)
-        {
+      def as_json(opts = {})
+        json_hash = {
           old_path: their_path,
           new_path: our_path,
           blob_icon: file_type_icon_class('file', our_mode, our_path),
           blob_path: namespace_project_blob_path(merge_request.project.namespace,
                                                  merge_request.project,
-                                                 ::File.join(merge_request.diff_refs.head_sha, our_path)),
-          sections: sections
+                                                 ::File.join(merge_request.diff_refs.head_sha, our_path))
         }
+
+        json_hash.tap do |json_hash|
+          if opts[:full_content]
+            json_hash[:content] = content
+            json_hash[:blob_ace_mode] = our_blob && our_blob.language.try(:ace_mode)
+          else
+            json_hash[:sections] = sections if type.text?
+            json_hash[:type] = type
+            json_hash[:content_path] = content_path
+          end
+        end
+      end
+
+      def content_path
+        conflict_for_path_namespace_project_merge_request_path(merge_request.project.namespace,
+                                                               merge_request.project,
+                                                               merge_request,
+                                                               old_path: their_path,
+                                                               new_path: our_path)
       end
 
       # Don't try to print merge_request or repository.
       def inspect
-        instance_variables = [:merge_file_result, :their_path, :our_path, :our_mode].map do |instance_variable|
+        instance_variables = [:merge_file_result, :their_path, :our_path, :our_mode, :type].map do |instance_variable|
           value = instance_variable_get("@#{instance_variable}")
 
           "#{instance_variable}=\"#{value}\""
diff --git a/lib/gitlab/conflict/file_collection.rb b/lib/gitlab/conflict/file_collection.rb
index bbd0427a2c82766707c6079447915148c065930e..fa5bd4649d473c619d1a4d75baaf1b703892aaed 100644
--- a/lib/gitlab/conflict/file_collection.rb
+++ b/lib/gitlab/conflict/file_collection.rb
@@ -30,6 +30,10 @@ module Gitlab
         end
       end
 
+      def file_for_path(old_path, new_path)
+        files.find { |file| file.their_path == old_path && file.our_path == new_path }
+      end
+
       def as_json(opts = nil)
         {
           target_branch: merge_request.target_branch,
diff --git a/lib/gitlab/conflict/parser.rb b/lib/gitlab/conflict/parser.rb
index 98e842cded36d2db71b0ef8dc1ce1a78aab8a568..ddd657903fb6ab08625218bc37ece897ec98143c 100644
--- a/lib/gitlab/conflict/parser.rb
+++ b/lib/gitlab/conflict/parser.rb
@@ -1,19 +1,24 @@
 module Gitlab
   module Conflict
     class Parser
-      class ParserError < StandardError
+      class UnresolvableError < StandardError
       end
 
-      class UnexpectedDelimiter < ParserError
+      class UnmergeableFile < UnresolvableError
       end
 
-      class MissingEndDelimiter < ParserError
+      class UnsupportedEncoding < UnresolvableError
+      end
+
+      # Recoverable errors - the conflict can be resolved in an editor, but not with
+      # sections.
+      class ParserError < StandardError
       end
 
-      class UnmergeableFile < ParserError
+      class UnexpectedDelimiter < ParserError
       end
 
-      class UnsupportedEncoding < ParserError
+      class MissingEndDelimiter < ParserError
       end
 
       def parse(text, our_path:, their_path:, parent_file: nil)
diff --git a/lib/gitlab/conflict/resolution_error.rb b/lib/gitlab/conflict/resolution_error.rb
new file mode 100644
index 0000000000000000000000000000000000000000..a0f2006bc245b7bc22811d11d85dda466a0fb9a6
--- /dev/null
+++ b/lib/gitlab/conflict/resolution_error.rb
@@ -0,0 +1,6 @@
+module Gitlab
+  module Conflict
+    class ResolutionError < StandardError
+    end
+  end
+end
diff --git a/spec/controllers/projects/merge_requests_controller_spec.rb b/spec/controllers/projects/merge_requests_controller_spec.rb
index d509f0f2b9685078bcae3402e7eed73d7799c3f1..d6980471ea49d852a86a32e202dedb2979835cdd 100644
--- a/spec/controllers/projects/merge_requests_controller_spec.rb
+++ b/spec/controllers/projects/merge_requests_controller_spec.rb
@@ -570,7 +570,7 @@ describe Projects::MergeRequestsController do
     context 'when the conflicts cannot be resolved in the UI' do
       before do
         allow_any_instance_of(Gitlab::Conflict::Parser).
-          to receive(:parse).and_raise(Gitlab::Conflict::Parser::UnexpectedDelimiter)
+          to receive(:parse).and_raise(Gitlab::Conflict::Parser::UnmergeableFile)
 
         get :conflicts,
             namespace_id: merge_request_with_conflicts.project.namespace.to_param,
@@ -597,6 +597,10 @@ describe Projects::MergeRequestsController do
             format: 'json'
       end
 
+      it 'matches the schema' do
+        expect(response).to match_response_schema('conflicts')
+      end
+
       it 'includes meta info about the MR' do
         expect(json_response['commit_message']).to include('Merge branch')
         expect(json_response['commit_sha']).to match(/\h{40}/)
@@ -658,26 +662,97 @@ describe Projects::MergeRequestsController do
     end
   end
 
+  describe 'GET conflict_for_path' do
+    let(:json_response) { JSON.parse(response.body) }
+
+    def conflict_for_path(path)
+      get :conflict_for_path,
+          namespace_id: merge_request_with_conflicts.project.namespace.to_param,
+          project_id: merge_request_with_conflicts.project.to_param,
+          id: merge_request_with_conflicts.iid,
+          old_path: path,
+          new_path: path,
+          format: 'json'
+    end
+
+    context 'when the conflicts cannot be resolved in the UI' do
+      before do
+        allow_any_instance_of(Gitlab::Conflict::Parser).
+          to receive(:parse).and_raise(Gitlab::Conflict::Parser::UnmergeableFile)
+
+        conflict_for_path('files/ruby/regex.rb')
+      end
+
+      it 'returns a 404 status code' do
+        expect(response).to have_http_status(:not_found)
+      end
+    end
+
+    context 'when the file does not exist cannot be resolved in the UI' do
+      before { conflict_for_path('files/ruby/regexp.rb') }
+
+      it 'returns a 404 status code' do
+        expect(response).to have_http_status(:not_found)
+      end
+    end
+
+    context 'with an existing file' do
+      let(:path) { 'files/ruby/regex.rb' }
+
+      before { conflict_for_path(path) }
+
+      it 'returns a 200 status code' do
+        expect(response).to have_http_status(:ok)
+      end
+
+      it 'returns the file in JSON format' do
+        content = merge_request_with_conflicts.conflicts.file_for_path(path, path).content
+
+        expect(json_response).to include('old_path' => path,
+                                         'new_path' => path,
+                                         'blob_icon' => 'file-text-o',
+                                         'blob_path' => a_string_ending_with(path),
+                                         'blob_ace_mode' => 'ruby',
+                                         'content' => content)
+      end
+    end
+  end
+
   context 'POST resolve_conflicts' do
     let(:json_response) { JSON.parse(response.body) }
     let!(:original_head_sha) { merge_request_with_conflicts.diff_head_sha }
 
-    def resolve_conflicts(sections)
+    def resolve_conflicts(files)
       post :resolve_conflicts,
            namespace_id: merge_request_with_conflicts.project.namespace.to_param,
            project_id: merge_request_with_conflicts.project.to_param,
            id: merge_request_with_conflicts.iid,
            format: 'json',
-           sections: sections,
+           files: files,
            commit_message: 'Commit message'
     end
 
     context 'with valid params' do
       before do
-        resolve_conflicts('2f6fcd96b88b36ce98c38da085c795a27d92a3dd_14_14' => 'head',
-                          '6eb14e00385d2fb284765eb1cd8d420d33d63fc9_9_9' => 'head',
-                          '6eb14e00385d2fb284765eb1cd8d420d33d63fc9_21_21' => 'origin',
-                          '6eb14e00385d2fb284765eb1cd8d420d33d63fc9_49_49' => 'origin')
+        resolved_files = [
+          {
+            'new_path' => 'files/ruby/popen.rb',
+            'old_path' => 'files/ruby/popen.rb',
+            'sections' => {
+              '2f6fcd96b88b36ce98c38da085c795a27d92a3dd_14_14' => 'head'
+            }
+          }, {
+            'new_path' => 'files/ruby/regex.rb',
+            'old_path' => 'files/ruby/regex.rb',
+            'sections' => {
+              '6eb14e00385d2fb284765eb1cd8d420d33d63fc9_9_9' => 'head',
+              '6eb14e00385d2fb284765eb1cd8d420d33d63fc9_21_21' => 'origin',
+              '6eb14e00385d2fb284765eb1cd8d420d33d63fc9_49_49' => 'origin'
+            }
+          }
+        ]
+
+        resolve_conflicts(resolved_files)
       end
 
       it 'creates a new commit on the branch' do
@@ -692,7 +767,23 @@ describe Projects::MergeRequestsController do
 
     context 'when sections are missing' do
       before do
-        resolve_conflicts('2f6fcd96b88b36ce98c38da085c795a27d92a3dd_14_14' => 'head')
+        resolved_files = [
+          {
+            'new_path' => 'files/ruby/popen.rb',
+            'old_path' => 'files/ruby/popen.rb',
+            'sections' => {
+              '2f6fcd96b88b36ce98c38da085c795a27d92a3dd_14_14' => 'head'
+            }
+          }, {
+            'new_path' => 'files/ruby/regex.rb',
+            'old_path' => 'files/ruby/regex.rb',
+            'sections' => {
+              '6eb14e00385d2fb284765eb1cd8d420d33d63fc9_9_9' => 'head'
+            }
+          }
+        ]
+
+        resolve_conflicts(resolved_files)
       end
 
       it 'returns a 400 error' do
@@ -700,7 +791,71 @@ describe Projects::MergeRequestsController do
       end
 
       it 'has a message with the name of the first missing section' do
-        expect(json_response['message']).to include('6eb14e00385d2fb284765eb1cd8d420d33d63fc9_9_9')
+        expect(json_response['message']).to include('6eb14e00385d2fb284765eb1cd8d420d33d63fc9_21_21')
+      end
+
+      it 'does not create a new commit' do
+        expect(original_head_sha).to eq(merge_request_with_conflicts.source_branch_head.sha)
+      end
+    end
+
+    context 'when files are missing' do
+      before do
+        resolved_files = [
+          {
+            'new_path' => 'files/ruby/regex.rb',
+            'old_path' => 'files/ruby/regex.rb',
+            'sections' => {
+              '6eb14e00385d2fb284765eb1cd8d420d33d63fc9_9_9' => 'head',
+              '6eb14e00385d2fb284765eb1cd8d420d33d63fc9_21_21' => 'origin',
+              '6eb14e00385d2fb284765eb1cd8d420d33d63fc9_49_49' => 'origin'
+            }
+          }
+        ]
+
+        resolve_conflicts(resolved_files)
+      end
+
+      it 'returns a 400 error' do
+        expect(response).to have_http_status(:bad_request)
+      end
+
+      it 'has a message with the name of the missing file' do
+        expect(json_response['message']).to include('files/ruby/popen.rb')
+      end
+
+      it 'does not create a new commit' do
+        expect(original_head_sha).to eq(merge_request_with_conflicts.source_branch_head.sha)
+      end
+    end
+
+    context 'when a file has identical content to the conflict' do
+      before do
+        resolved_files = [
+          {
+            'new_path' => 'files/ruby/popen.rb',
+            'old_path' => 'files/ruby/popen.rb',
+            'content' => merge_request_with_conflicts.conflicts.file_for_path('files/ruby/popen.rb', 'files/ruby/popen.rb').content
+          }, {
+            'new_path' => 'files/ruby/regex.rb',
+            'old_path' => 'files/ruby/regex.rb',
+            'sections' => {
+              '6eb14e00385d2fb284765eb1cd8d420d33d63fc9_9_9' => 'head',
+              '6eb14e00385d2fb284765eb1cd8d420d33d63fc9_21_21' => 'origin',
+              '6eb14e00385d2fb284765eb1cd8d420d33d63fc9_49_49' => 'origin'
+            }
+          }
+        ]
+
+        resolve_conflicts(resolved_files)
+      end
+
+      it 'returns a 400 error' do
+        expect(response).to have_http_status(:bad_request)
+      end
+
+      it 'has a message with the path of the problem file' do
+        expect(json_response['message']).to include('files/ruby/popen.rb')
       end
 
       it 'does not create a new commit' do
diff --git a/spec/features/atom/users_spec.rb b/spec/features/atom/users_spec.rb
index a8833194421fca4eb355e158ad1c1a2cf8935aa9..f8c3ccb416b022a802d7438e728233f6877f96a1 100644
--- a/spec/features/atom/users_spec.rb
+++ b/spec/features/atom/users_spec.rb
@@ -53,7 +53,7 @@ describe "User Feed", feature: true  do
       end
 
       it 'has XHTML summaries in issue descriptions' do
-        expect(body).to match /we have a bug!<\/p>\n\n<hr ?\/>\n\n<p>I guess/
+        expect(body).to match /we have a bug!<\/p>\n\n<hr ?\/>\n\n<p dir="auto">I guess/
       end
 
       it 'has XHTML summaries in notes' do
diff --git a/spec/features/merge_requests/conflicts_spec.rb b/spec/features/merge_requests/conflicts_spec.rb
index 759edf8ec80c5bb968604701df27ce9e3a987504..d258ff52bbb7eb326cc77cdb569302f96a4ff5aa 100644
--- a/spec/features/merge_requests/conflicts_spec.rb
+++ b/spec/features/merge_requests/conflicts_spec.rb
@@ -12,29 +12,139 @@ feature 'Merge request conflict resolution', js: true, feature: true do
     end
   end
 
-  context 'when a merge request can be resolved in the UI' do
-    let(:merge_request) { create_merge_request('conflict-resolvable') }
+  shared_examples "conflicts are resolved in Interactive mode" do
+    it 'conflicts are resolved in Interactive mode' do
+      within find('.files-wrapper .diff-file', text: 'files/ruby/popen.rb') do
+        click_button 'Use ours'
+      end
+
+      within find('.files-wrapper .diff-file', text: 'files/ruby/regex.rb') do
+        all('button', text: 'Use ours').each do |button|
+          button.click
+        end
+      end
+
+      click_button 'Commit conflict resolution'
+      wait_for_ajax
+
+      expect(page).to have_content('All merge conflicts were resolved')
+      merge_request.reload_diff
+
+      click_on 'Changes'
+      wait_for_ajax
+
+      within find('.diff-file', text: 'files/ruby/popen.rb') do
+        expect(page).to have_selector('.line_content.new', text: "vars = { 'PWD' => path }")
+        expect(page).to have_selector('.line_content.new', text: "options = { chdir: path }")
+      end
+
+      within find('.diff-file', text: 'files/ruby/regex.rb') do
+        expect(page).to have_selector('.line_content.new', text: "def username_regexp")
+        expect(page).to have_selector('.line_content.new', text: "def project_name_regexp")
+        expect(page).to have_selector('.line_content.new', text: "def path_regexp")
+        expect(page).to have_selector('.line_content.new', text: "def archive_formats_regexp")
+        expect(page).to have_selector('.line_content.new', text: "def git_reference_regexp")
+        expect(page).to have_selector('.line_content.new', text: "def default_regexp")
+      end
+    end
+  end
 
+  shared_examples "conflicts are resolved in Edit inline mode" do
+    it 'conflicts are resolved in Edit inline mode' do
+      expect(find('#conflicts')).to have_content('popen.rb')
+
+      within find('.files-wrapper .diff-file', text: 'files/ruby/popen.rb') do
+        click_button 'Edit inline'
+        wait_for_ajax
+        execute_script('ace.edit($(".files-wrapper .diff-file pre")[0]).setValue("One morning");')
+      end
+
+      within find('.files-wrapper .diff-file', text: 'files/ruby/regex.rb') do
+        click_button 'Edit inline'
+        wait_for_ajax
+        execute_script('ace.edit($(".files-wrapper .diff-file pre")[1]).setValue("Gregor Samsa woke from troubled dreams");')
+      end
+
+      click_button 'Commit conflict resolution'
+      wait_for_ajax
+      expect(page).to have_content('All merge conflicts were resolved')
+      merge_request.reload_diff
+
+      click_on 'Changes'
+      wait_for_ajax
+
+      expect(page).to have_content('One morning')
+      expect(page).to have_content('Gregor Samsa woke from troubled dreams')
+    end
+  end
+
+  context 'can be resolved in the UI' do
     before do
       project.team << [user, :developer]
       login_as(user)
-
-      visit namespace_project_merge_request_path(project.namespace, project, merge_request)
     end
 
-    it 'shows a link to the conflict resolution page' do
-      expect(page).to have_link('conflicts', href: /\/conflicts\Z/)
+    context 'the conflicts are resolvable' do
+      let(:merge_request) { create_merge_request('conflict-resolvable') }
+
+      before { visit namespace_project_merge_request_path(project.namespace, project, merge_request) }
+
+      it 'shows a link to the conflict resolution page' do
+        expect(page).to have_link('conflicts', href: /\/conflicts\Z/)
+      end
+
+      context 'in Inline view mode' do
+        before { click_link('conflicts', href: /\/conflicts\Z/) }
+
+        include_examples "conflicts are resolved in Interactive mode"
+        include_examples "conflicts are resolved in Edit inline mode"
+      end
+
+      context 'in Parallel view mode' do
+        before do
+          click_link('conflicts', href: /\/conflicts\Z/) 
+          click_button 'Side-by-side'
+        end
+
+        include_examples "conflicts are resolved in Interactive mode"
+        include_examples "conflicts are resolved in Edit inline mode"
+      end
     end
 
-    context 'visiting the conflicts resolution page' do
-      before { click_link('conflicts', href: /\/conflicts\Z/) }
+    context 'the conflict contain markers' do
+      let(:merge_request) { create_merge_request('conflict-contains-conflict-markers') }
 
-      it 'shows the conflicts' do
-        begin
-          expect(find('#conflicts')).to have_content('popen.rb')
-        rescue Capybara::Poltergeist::JavascriptError
-          retry
+      before do
+        visit namespace_project_merge_request_path(project.namespace, project, merge_request)
+        click_link('conflicts', href: /\/conflicts\Z/)
+      end
+
+      it 'conflicts can not be resolved in Interactive mode' do
+        within find('.files-wrapper .diff-file', text: 'files/markdown/ruby-style-guide.md') do
+          expect(page).not_to have_content 'Interactive mode'
+          expect(page).not_to have_content 'Edit inline'
+        end
+      end
+
+      it 'conflicts are resolved in Edit inline mode' do
+        within find('.files-wrapper .diff-file', text: 'files/markdown/ruby-style-guide.md') do
+          wait_for_ajax
+          execute_script('ace.edit($(".files-wrapper .diff-file pre")[0]).setValue("Gregor Samsa woke from troubled dreams");')
         end
+
+        click_button 'Commit conflict resolution'
+        wait_for_ajax
+
+        expect(page).to have_content('All merge conflicts were resolved')
+
+        merge_request.reload_diff
+
+        click_on 'Changes'
+        wait_for_ajax
+        find('.click-to-expand').click
+        wait_for_ajax
+
+        expect(page).to have_content('Gregor Samsa woke from troubled dreams')
       end
     end
   end
@@ -42,7 +152,6 @@ feature 'Merge request conflict resolution', js: true, feature: true do
   UNRESOLVABLE_CONFLICTS = {
     'conflict-too-large' => 'when the conflicts contain a large file',
     'conflict-binary-file' => 'when the conflicts contain a binary file',
-    'conflict-contains-conflict-markers' => 'when the conflicts contain a file with ambiguous conflict markers',
     'conflict-missing-side' => 'when the conflicts contain a file edited in one branch and deleted in another',
     'conflict-non-utf8' => 'when the conflicts contain a non-UTF-8 file',
   }
diff --git a/spec/features/projects/files/find_file_keyboard_spec.rb b/spec/features/projects/files/find_file_keyboard_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..fc88fd74af879e53c28b1c895c3d5305146d2239
--- /dev/null
+++ b/spec/features/projects/files/find_file_keyboard_spec.rb
@@ -0,0 +1,42 @@
+require 'spec_helper'
+
+feature 'Find file keyboard shortcuts', feature: true, js: true do
+  include WaitForAjax
+
+  let(:user) { create(:user) }
+  let(:project) { create(:project) }
+
+  before do
+    project.team << [user, :master]
+    login_as user
+
+    visit namespace_project_find_file_path(project.namespace, project, project.repository.root_ref)
+
+    wait_for_ajax
+  end
+
+  it 'opens file when pressing enter key' do
+    fill_in 'file_find', with: 'CHANGELOG'
+
+    find('#file_find').native.send_keys(:enter)
+
+    expect(page).to have_selector('.blob-content-holder')
+
+    page.within('.file-title') do
+      expect(page).to have_content('CHANGELOG')
+    end
+  end
+
+  it 'navigates files with arrow keys' do
+    fill_in 'file_find', with: 'application.'
+
+    find('#file_find').native.send_keys(:down)
+    find('#file_find').native.send_keys(:enter)
+
+    expect(page).to have_selector('.blob-content-holder')
+
+    page.within('.file-title') do
+      expect(page).to have_content('application.js')
+    end
+  end
+end
diff --git a/spec/features/users_spec.rb b/spec/features/users_spec.rb
index ec4c4d62f535cef814da21e1c19cc6c98928379f..111ca7f7a703a05963cb8f8e26e1aba47afab244 100644
--- a/spec/features/users_spec.rb
+++ b/spec/features/users_spec.rb
@@ -51,6 +51,18 @@ feature 'Users', feature: true, js: true do
       expect(current_path).to eq user_path(user)
       expect(page).to have_text(user.name)
     end
+
+    scenario '/u/user1/groups redirects to user groups page' do
+      visit '/u/user1/groups'
+
+      expect(current_path).to eq user_groups_path(user)
+    end
+
+    scenario '/u/user1/projects redirects to user projects page' do
+      visit '/u/user1/projects'
+
+      expect(current_path).to eq user_projects_path(user)
+    end
   end
 
   feature 'username validation' do
diff --git a/spec/fixtures/api/schemas/conflicts.json b/spec/fixtures/api/schemas/conflicts.json
new file mode 100644
index 0000000000000000000000000000000000000000..a947783d505c59690b3c085feedd349b9cfcf83e
--- /dev/null
+++ b/spec/fixtures/api/schemas/conflicts.json
@@ -0,0 +1,137 @@
+{
+  "type": "object",
+  "required": [
+    "commit_message",
+    "commit_sha",
+    "source_branch",
+    "target_branch",
+    "files"
+  ],
+  "properties": {
+    "commit_message": {"type": "string"},
+    "commit_sha": {"type": "string", "pattern": "^[0-9a-f]{40}$"},
+    "source_branch": {"type": "string"},
+    "target_branch": {"type": "string"},
+    "files": {
+      "type": "array",
+      "items": {
+        "oneOf": [
+          { "$ref": "#/definitions/conflict-text-with-sections" },
+          { "$ref": "#/definitions/conflict-text-for-editor" }
+        ]
+      }
+    }
+  },
+  "definitions": {
+    "conflict-base": {
+      "type": "object",
+      "required": [
+        "old_path",
+        "new_path",
+        "blob_icon",
+        "blob_path"
+      ],
+      "properties": {
+        "old_path": {"type": "string"},
+        "new_path": {"type": "string"},
+        "blob_icon": {"type": "string"},
+        "blob_path": {"type": "string"}
+      }
+    },
+    "conflict-text-for-editor": {
+      "allOf": [
+        {"$ref": "#/definitions/conflict-base"},
+        {
+          "type": "object",
+          "required": [
+            "type",
+            "content_path"
+          ],
+          "properties": {
+            "type": {"type": {"enum": ["text-editor"]}},
+            "content_path": {"type": "string"}
+          }
+        }
+      ]
+    },
+    "conflict-text-with-sections": {
+      "allOf": [
+        {"$ref": "#/definitions/conflict-base"},
+        {
+          "type": "object",
+          "required": [
+            "type",
+            "content_path",
+            "sections"
+          ],
+          "properties": {
+            "type": {"type": {"enum": ["text"]}},
+            "content_path": {"type": "string"},
+            "sections": {
+              "type": "array",
+              "items": {
+                "oneOf": [
+                  { "$ref": "#/definitions/section-context" },
+                  { "$ref": "#/definitions/section-conflict" }
+                ]
+              }
+            }
+          }
+        }
+      ]
+    },
+    "section-base": {
+      "type": "object",
+      "required": [
+        "conflict",
+        "lines"
+      ],
+      "properties": {
+        "conflict": {"type": "boolean"},
+        "lines": {
+          "type": "array",
+          "items": {
+            "type": "object",
+            "required": [
+              "old_line",
+              "new_line",
+              "text",
+              "rich_text"
+            ],
+            "properties": {
+              "type": {"type": "string"},
+              "old_line": {"type": "string"},
+              "new_line": {"type": "string"},
+              "text": {"type": "string"},
+              "rich_text": {"type": "string"}
+            }
+          }
+        }
+      }
+    },
+    "section-context": {
+      "allOf": [
+        {"$ref": "#/definitions/section-base"},
+        {
+          "type": "object",
+          "properties": {
+            "conflict": {"enum": [false]}
+          }
+        }
+      ]
+    },
+    "section-conflict": {
+      "allOf": [
+        {"$ref": "#/definitions/section-base"},
+        {
+          "type": "object",
+          "required": ["id"],
+          "properties": {
+            "conflict": {"enum": [true]},
+            "id": {"type": "string"}
+          }
+        }
+      ]
+    }
+  }
+}
diff --git a/spec/lib/banzai/object_renderer_spec.rb b/spec/lib/banzai/object_renderer_spec.rb
index 90da78a67dd666d8d08832583e805c5ead564614..6bcda87c99971cd00bdce9c0a86b4da8f0f7bb6d 100644
--- a/spec/lib/banzai/object_renderer_spec.rb
+++ b/spec/lib/banzai/object_renderer_spec.rb
@@ -24,7 +24,7 @@ describe Banzai::ObjectRenderer do
         with(an_instance_of(Array)).
         and_call_original
 
-      expect(object).to receive(:redacted_note_html=).with('<p>hello</p>')
+      expect(object).to receive(:redacted_note_html=).with('<p dir="auto">hello</p>')
       expect(object).to receive(:user_visible_reference_count=).with(0)
 
       renderer.render([object], :note)
@@ -92,10 +92,10 @@ describe Banzai::ObjectRenderer do
       docs = renderer.render_attributes(objects, :note)
 
       expect(docs[0]).to be_an_instance_of(Nokogiri::HTML::DocumentFragment)
-      expect(docs[0].to_html).to eq('<p>hello</p>')
+      expect(docs[0].to_html).to eq('<p dir="auto">hello</p>')
 
       expect(docs[1]).to be_an_instance_of(Nokogiri::HTML::DocumentFragment)
-      expect(docs[1].to_html).to eq('<p>bye</p>')
+      expect(docs[1].to_html).to eq('<p dir="auto">bye</p>')
     end
 
     it 'returns when no objects to render' do
diff --git a/spec/lib/banzai/pipeline/description_pipeline_spec.rb b/spec/lib/banzai/pipeline/description_pipeline_spec.rb
index 76f4207181065fb78202c6845c3f88d580ffec93..8cce1b96698da7b3b81358fb8fe45a274ddc7169 100644
--- a/spec/lib/banzai/pipeline/description_pipeline_spec.rb
+++ b/spec/lib/banzai/pipeline/description_pipeline_spec.rb
@@ -4,11 +4,11 @@ describe Banzai::Pipeline::DescriptionPipeline do
   def parse(html)
     # When we pass HTML to Redcarpet, it gets wrapped in `p` tags...
     # ...except when we pass it pre-wrapped text. Rabble rabble.
-    unwrap = !html.start_with?('<p>')
+    unwrap = !html.start_with?('<p ')
 
     output = described_class.to_html(html, project: spy)
 
-    output.gsub!(%r{\A<p>(.*)</p>(.*)\z}, '\1\2') if unwrap
+    output.gsub!(%r{\A<p dir="auto">(.*)</p>(.*)\z}, '\1\2') if unwrap
 
     output
   end
@@ -27,11 +27,17 @@ describe Banzai::Pipeline::DescriptionPipeline do
     end
   end
 
-  %w(b i strong em a ins del sup sub p).each do |elem|
+  %w(b i strong em a ins del sup sub).each do |elem|
     it "still allows '#{elem}' elements" do
       exp = act = "<#{elem}>Description</#{elem}>"
 
       expect(parse(act).strip).to eq exp
     end
   end
+
+  it "still allows 'p' elements" do
+    exp = act = "<p dir=\"auto\">Description</p>"
+
+    expect(parse(act).strip).to eq exp
+  end
 end
diff --git a/spec/lib/gitlab/conflict/file_spec.rb b/spec/lib/gitlab/conflict/file_spec.rb
index 60020487061b849dcda0dd40dea6ca0c783eea37..648d342ecf8e7d20f3c6e329c465f61968e37293 100644
--- a/spec/lib/gitlab/conflict/file_spec.rb
+++ b/spec/lib/gitlab/conflict/file_spec.rb
@@ -257,5 +257,16 @@ FILE
     it 'includes the blob icon for the file' do
       expect(conflict_file.as_json[:blob_icon]).to eq('file-text-o')
     end
+
+    context 'with the full_content option passed' do
+      it 'includes the full content of the conflict' do
+        expect(conflict_file.as_json(full_content: true)).to have_key(:content)
+      end
+
+      it 'includes the detected language of the conflict file' do
+        expect(conflict_file.as_json(full_content: true)[:blob_ace_mode]).
+          to eq('ruby')
+      end
+    end
   end
 end
diff --git a/spec/models/ci/pipeline_spec.rb b/spec/models/ci/pipeline_spec.rb
index 85820a9dc5729433c7d51ac3170c8f7487025797..caf191e11ae3dcfe0f60a693da83a9f000e23076 100644
--- a/spec/models/ci/pipeline_spec.rb
+++ b/spec/models/ci/pipeline_spec.rb
@@ -187,33 +187,24 @@ describe Ci::Pipeline, models: true do
       end
     end
 
-    describe "merge request metrics" do
+    describe 'merge request metrics' do
       let(:project) { FactoryGirl.create :project }
       let(:pipeline) { FactoryGirl.create(:ci_empty_pipeline, status: 'created', project: project, ref: 'master', sha: project.repository.commit('master').id) }
       let!(:merge_request) { create(:merge_request, source_project: project, source_branch: pipeline.ref) }
 
-      context 'when transitioning to running' do
-        it 'records the build start time' do
-          time = Time.now
-          Timecop.freeze(time) { build.run }
-
-          expect(merge_request.reload.metrics.latest_build_started_at).to be_within(1.second).of(time)
-        end
-
-        it 'clears the build end time' do
-          build.run
+      before do
+        expect(PipelineMetricsWorker).to receive(:perform_async).with(pipeline.id)
+      end
 
-          expect(merge_request.reload.metrics.latest_build_finished_at).to be_nil
+      context 'when transitioning to running' do
+        it 'schedules metrics workers' do
+          pipeline.run
         end
       end
 
       context 'when transitioning to success' do
-        it 'records the build end time' do
-          build.run
-          time = Time.now
-          Timecop.freeze(time) { build.success }
-
-          expect(merge_request.reload.metrics.latest_build_finished_at).to be_within(1.second).of(time)
+        it 'schedules metrics workers' do
+          pipeline.succeed
         end
       end
     end
diff --git a/spec/models/concerns/cache_markdown_field_spec.rb b/spec/models/concerns/cache_markdown_field_spec.rb
index 15cd3a7ed7067184840ec27bbabd207dcf90001a..2e3702f7520c401a9094bba865db4cc481efef10 100644
--- a/spec/models/concerns/cache_markdown_field_spec.rb
+++ b/spec/models/concerns/cache_markdown_field_spec.rb
@@ -64,7 +64,7 @@ describe CacheMarkdownField do
   let(:html) { "<p><code>Foo</code></p>" }
 
   let(:updated_markdown) { "`Bar`" }
-  let(:updated_html) { "<p><code>Bar</code></p>" }
+  let(:updated_html) { "<p dir=\"auto\"><code>Bar</code></p>" }
 
   subject { ThingWithMarkdownFields.new(foo: markdown, foo_html: html) }
 
diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb
index 2167a988f998629b29f0833edbe01d4bde2ac134..1acc8d748afb5ab481bac85ea9e2f90d6cc30460 100644
--- a/spec/models/merge_request_spec.rb
+++ b/spec/models/merge_request_spec.rb
@@ -1155,12 +1155,6 @@ describe MergeRequest, models: true do
       expect(merge_request.conflicts_can_be_resolved_in_ui?).to be_falsey
     end
 
-    it 'returns a falsey value when the conflicts contain a file with ambiguous conflict markers' do
-      merge_request = create_merge_request('conflict-contains-conflict-markers')
-
-      expect(merge_request.conflicts_can_be_resolved_in_ui?).to be_falsey
-    end
-
     it 'returns a falsey value when the conflicts contain a file edited in one branch and deleted in another' do
       merge_request = create_merge_request('conflict-missing-side')
 
@@ -1172,6 +1166,12 @@ describe MergeRequest, models: true do
 
       expect(merge_request.conflicts_can_be_resolved_in_ui?).to be_truthy
     end
+
+    it 'returns a truthy value when the conflicts have to be resolved in an editor' do
+      merge_request = create_merge_request('conflict-contains-conflict-markers')
+
+      expect(merge_request.conflicts_can_be_resolved_in_ui?).to be_truthy
+    end
   end
 
   describe "#forked_source_project_missing?" do
diff --git a/spec/routing/routing_spec.rb b/spec/routing/routing_spec.rb
index 488dc1a63b0d0a1eb6bd5d0d3d5cbb91c7ebccfe..c18a2d55e438607d69acfab7cf83450a96a50527 100644
--- a/spec/routing/routing_spec.rb
+++ b/spec/routing/routing_spec.rb
@@ -15,27 +15,27 @@ describe UsersController, "routing" do
   end
 
   it "to #groups" do
-    expect(get("/u/User/groups")).to route_to('users#groups', username: 'User')
+    expect(get("/users/User/groups")).to route_to('users#groups', username: 'User')
   end
 
   it "to #projects" do
-    expect(get("/u/User/projects")).to route_to('users#projects', username: 'User')
+    expect(get("/users/User/projects")).to route_to('users#projects', username: 'User')
   end
 
   it "to #contributed" do
-    expect(get("/u/User/contributed")).to route_to('users#contributed', username: 'User')
+    expect(get("/users/User/contributed")).to route_to('users#contributed', username: 'User')
   end
 
   it "to #snippets" do
-    expect(get("/u/User/snippets")).to route_to('users#snippets', username: 'User')
+    expect(get("/users/User/snippets")).to route_to('users#snippets', username: 'User')
   end
 
   it "to #calendar" do
-    expect(get("/u/User/calendar")).to route_to('users#calendar', username: 'User')
+    expect(get("/users/User/calendar")).to route_to('users#calendar', username: 'User')
   end
 
   it "to #calendar_activities" do
-    expect(get("/u/User/calendar_activities")).to route_to('users#calendar_activities', username: 'User')
+    expect(get("/users/User/calendar_activities")).to route_to('users#calendar_activities', username: 'User')
   end
 end
 
diff --git a/spec/services/merge_requests/resolve_service_spec.rb b/spec/services/merge_requests/resolve_service_spec.rb
index d71932458fa049a80140f6337734b7fd6fe16c8d..388abb6a0dfc3045f0b82d7bdc0450a9f89fb54a 100644
--- a/spec/services/merge_requests/resolve_service_spec.rb
+++ b/spec/services/merge_requests/resolve_service_spec.rb
@@ -24,15 +24,26 @@ describe MergeRequests::ResolveService do
   end
 
   describe '#execute' do
-    context 'with valid params' do
+    context 'with section params' do
       let(:params) do
         {
-          sections: {
-            '2f6fcd96b88b36ce98c38da085c795a27d92a3dd_14_14' => 'head',
-            '6eb14e00385d2fb284765eb1cd8d420d33d63fc9_9_9' => 'head',
-            '6eb14e00385d2fb284765eb1cd8d420d33d63fc9_21_21' => 'origin',
-            '6eb14e00385d2fb284765eb1cd8d420d33d63fc9_49_49' => 'origin'
-          },
+          files: [
+            {
+              old_path: 'files/ruby/popen.rb',
+              new_path: 'files/ruby/popen.rb',
+              sections: {
+                '2f6fcd96b88b36ce98c38da085c795a27d92a3dd_14_14' => 'head'
+              }
+            }, {
+              old_path: 'files/ruby/regex.rb',
+              new_path: 'files/ruby/regex.rb',
+              sections: {
+                '6eb14e00385d2fb284765eb1cd8d420d33d63fc9_9_9' => 'head',
+                '6eb14e00385d2fb284765eb1cd8d420d33d63fc9_21_21' => 'origin',
+                '6eb14e00385d2fb284765eb1cd8d420d33d63fc9_49_49' => 'origin'
+              }
+            }
+          ],
           commit_message: 'This is a commit message!'
         }
       end
@@ -49,7 +60,7 @@ describe MergeRequests::ResolveService do
         it 'creates a commit with the correct parents' do
           expect(merge_request.source_branch_head.parents.map(&:id)).
             to eq(['1450cd639e0bc6721eb02800169e464f212cde06',
-                   '75284c70dd26c87f2a3fb65fd5a1f0b0138d3a6b'])
+                   '824be604a34828eb682305f0d963056cfac87b2d'])
         end
       end
 
@@ -74,8 +85,96 @@ describe MergeRequests::ResolveService do
       end
     end
 
-    context 'when a resolution is missing' do
-      let(:invalid_params) { { sections: { '2f6fcd96b88b36ce98c38da085c795a27d92a3dd_14_14' => 'head' } } }
+    context 'with content and sections params' do
+      let(:popen_content) { "class Popen\nend" }
+
+      let(:params) do
+        {
+          files: [
+            {
+              old_path: 'files/ruby/popen.rb',
+              new_path: 'files/ruby/popen.rb',
+              content: popen_content
+            }, {
+              old_path: 'files/ruby/regex.rb',
+              new_path: 'files/ruby/regex.rb',
+              sections: {
+                '6eb14e00385d2fb284765eb1cd8d420d33d63fc9_9_9' => 'head',
+                '6eb14e00385d2fb284765eb1cd8d420d33d63fc9_21_21' => 'origin',
+                '6eb14e00385d2fb284765eb1cd8d420d33d63fc9_49_49' => 'origin'
+              }
+            }
+          ],
+          commit_message: 'This is a commit message!'
+        }
+      end
+
+      before do
+        MergeRequests::ResolveService.new(project, user, params).execute(merge_request)
+      end
+
+      it 'creates a commit with the message' do
+        expect(merge_request.source_branch_head.message).to eq(params[:commit_message])
+      end
+
+      it 'creates a commit with the correct parents' do
+        expect(merge_request.source_branch_head.parents.map(&:id)).
+          to eq(['1450cd639e0bc6721eb02800169e464f212cde06',
+                 '824be604a34828eb682305f0d963056cfac87b2d'])
+      end
+
+      it 'sets the content to the content given' do
+        blob = merge_request.source_project.repository.blob_at(merge_request.source_branch_head.sha,
+                                                               'files/ruby/popen.rb')
+
+        expect(blob.data).to eq(popen_content)
+      end
+    end
+
+    context 'when a resolution section is missing' do
+      let(:invalid_params) do
+        {
+          files: [
+            {
+              old_path: 'files/ruby/popen.rb',
+              new_path: 'files/ruby/popen.rb',
+              content: ''
+            }, {
+              old_path: 'files/ruby/regex.rb',
+              new_path: 'files/ruby/regex.rb',
+              sections: { '6eb14e00385d2fb284765eb1cd8d420d33d63fc9_9_9' => 'head' }
+            }
+          ],
+          commit_message: 'This is a commit message!'
+        }
+      end
+
+      let(:service) { MergeRequests::ResolveService.new(project, user, invalid_params) }
+
+      it 'raises a MissingResolution error' do
+        expect { service.execute(merge_request) }.
+          to raise_error(Gitlab::Conflict::File::MissingResolution)
+      end
+    end
+
+    context 'when the content of a file is unchanged' do
+      let(:invalid_params) do
+        {
+          files: [
+            {
+              old_path: 'files/ruby/popen.rb',
+              new_path: 'files/ruby/popen.rb',
+              content: ''
+            }, {
+              old_path: 'files/ruby/regex.rb',
+              new_path: 'files/ruby/regex.rb',
+              content: merge_request.conflicts.file_for_path('files/ruby/regex.rb', 'files/ruby/regex.rb').content
+            }
+          ],
+          commit_message: 'This is a commit message!'
+        }
+      end
+
       let(:service) { MergeRequests::ResolveService.new(project, user, invalid_params) }
 
       it 'raises a MissingResolution error' do
@@ -83,5 +182,27 @@ describe MergeRequests::ResolveService do
           to raise_error(Gitlab::Conflict::File::MissingResolution)
       end
     end
+
+    context 'when a file is missing' do
+      let(:invalid_params) do
+        {
+          files: [
+            {
+              old_path: 'files/ruby/popen.rb',
+              new_path: 'files/ruby/popen.rb',
+              content: ''
+            }
+          ],
+          commit_message: 'This is a commit message!'
+        }
+      end
+
+      let(:service) { MergeRequests::ResolveService.new(project, user, invalid_params) }
+
+      it 'raises a MissingFiles error' do
+        expect { service.execute(merge_request) }.
+          to raise_error(MergeRequests::ResolveService::MissingFiles)
+      end
+    end
   end
 end
diff --git a/spec/support/test_env.rb b/spec/support/test_env.rb
index ad8ae763f6dce8300b1872e5f9d2c30c6e96f064..c79975d8667ef71be6aafc13edd5b60521ebf36c 100644
--- a/spec/support/test_env.rb
+++ b/spec/support/test_env.rb
@@ -27,10 +27,10 @@ module TestEnv
     'expand-collapse-lines'              => '238e82d',
     'video'                              => '8879059',
     'crlf-diff'                          => '5938907',
-    'conflict-start'                     => '75284c7',
+    'conflict-start'                     => '824be60',
     'conflict-resolvable'                => '1450cd6',
     'conflict-binary-file'               => '259a6fb',
-    'conflict-contains-conflict-markers' => '5e0964c',
+    'conflict-contains-conflict-markers' => '78a3086',
     'conflict-missing-side'              => 'eb227b3',
     'conflict-non-utf8'                  => 'd0a293c',
     'conflict-too-large'                 => '39fa04f',
@@ -98,7 +98,9 @@ module TestEnv
 
   def setup_gitlab_shell
     unless File.directory?(Gitlab.config.gitlab_shell.path)
-      `rake gitlab:shell:install`
+      unless system('rake', 'gitlab:shell:install')
+        raise 'Can`t clone gitlab-shell'
+      end
     end
   end
 
diff --git a/spec/workers/pipeline_metrics_worker_spec.rb b/spec/workers/pipeline_metrics_worker_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..232478c973526287049eee8ae7a4991dac3a07a4
--- /dev/null
+++ b/spec/workers/pipeline_metrics_worker_spec.rb
@@ -0,0 +1,46 @@
+require 'spec_helper'
+
+describe PipelineMetricsWorker do
+  let(:project) { create(:project) }
+  let!(:merge_request) { create(:merge_request, source_project: project, source_branch: pipeline.ref) }
+
+  let(:pipeline) do
+    create(:ci_empty_pipeline,
+           status: status,
+           project: project,
+           ref: 'master',
+           sha: project.repository.commit('master').id,
+           started_at: 1.hour.ago,
+           finished_at: Time.now)
+  end
+
+  describe '#perform' do
+    subject { described_class.new.perform(pipeline.id) }
+
+    context 'when pipeline is running' do
+      let(:status) { 'running' }
+
+      it 'records the build start time' do
+        subject
+
+        expect(merge_request.reload.metrics.latest_build_started_at).to be_within(1.second).of(pipeline.started_at)
+      end
+
+      it 'clears the build end time' do
+        subject
+
+        expect(merge_request.reload.metrics.latest_build_finished_at).to be_nil
+      end
+    end
+
+    context 'when pipeline succeeded' do
+      let(:status) { 'success' }
+
+      it 'records the build end time' do
+        subject
+
+        expect(merge_request.reload.metrics.latest_build_finished_at).to be_within(1.second).of(pipeline.finished_at)
+      end
+    end
+  end
+end