diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb
index 4e8be8adb19627fe439d88ad94411923a03a5cfe..a687c021b0657844efce7bc782803fc5f5fc2837 100644
--- a/app/models/ci/pipeline.rb
+++ b/app/models/ci/pipeline.rb
@@ -18,6 +18,7 @@ module Ci
     belongs_to :user
     belongs_to :auto_canceled_by, class_name: 'Ci::Pipeline'
     belongs_to :pipeline_schedule, class_name: 'Ci::PipelineSchedule'
+    belongs_to :merge_request, class_name: 'MergeRequest'
 
     has_internal_id :iid, scope: :project, presence: false, init: ->(s) do
       s&.project&.pipelines&.maximum(:iid) || s&.project&.pipelines&.count
@@ -61,6 +62,9 @@ module Ci
 
     validates :sha, presence: { unless: :importing? }
     validates :ref, presence: { unless: :importing? }
+    validates :merge_request, presence: { if: :merge_request? }
+    validates :merge_request, absence: { unless: :merge_request? }
+    validates :tag, inclusion: { in: [false], if: :merge_request? }
     validates :status, presence: { unless: :importing? }
     validate :valid_commit_sha, unless: :importing?
 
@@ -183,6 +187,13 @@ module Ci
 
     scope :internal, -> { where(source: internal_sources) }
 
+    scope :sort_by_merge_request_pipelines, -> do
+      sql = 'CASE ci_pipelines.source WHEN (?) THEN 0 ELSE 1 END, ci_pipelines.id DESC'
+      query = ActiveRecord::Base.send(:sanitize_sql_array, [sql, sources[:merge_request]]) # rubocop:disable GitlabSecurity/PublicSend
+
+      order(query)
+    end
+
     scope :for_user, -> (user) { where(user: user) }
 
     # Returns the pipelines in descending order (= newest first), optionally
@@ -384,7 +395,7 @@ module Ci
     end
 
     def branch?
-      !tag?
+      !tag? && !merge_request?
     end
 
     def stuck?
@@ -631,7 +642,12 @@ module Ci
 
     # All the merge requests for which the current pipeline runs/ran against
     def all_merge_requests
-      @all_merge_requests ||= project.merge_requests.where(source_branch: ref)
+      @all_merge_requests ||=
+        if merge_request?
+          project.merge_requests.where(id: merge_request.id)
+        else
+          project.merge_requests.where(source_branch: ref)
+        end
     end
 
     def detailed_status(current_user)
@@ -708,6 +724,8 @@ module Ci
     def git_ref
       if branch?
         Gitlab::Git::BRANCH_REF_PREFIX + ref.to_s
+      elsif merge_request?
+        Gitlab::Git::BRANCH_REF_PREFIX + ref.to_s
       elsif tag?
         Gitlab::Git::TAG_REF_PREFIX + ref.to_s
       else
diff --git a/app/models/ci/pipeline_enums.rb b/app/models/ci/pipeline_enums.rb
index 0cf07e8af8aa731f5c211173814067a8266e71b0..720f8a306db71fb7ff19594a391f7aebb3d834be 100644
--- a/app/models/ci/pipeline_enums.rb
+++ b/app/models/ci/pipeline_enums.rb
@@ -21,7 +21,8 @@ module Ci
         trigger: 3,
         schedule: 4,
         api: 5,
-        external: 6
+        external: 6,
+        merge_request: 10
       }
     end
   end
diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb
index 3441150149c1b9192912a33f0d0b3b597d677f6c..23b677a9c7eeb0d56d05d9c93ffc23c19b307e80 100644
--- a/app/models/merge_request.rb
+++ b/app/models/merge_request.rb
@@ -65,6 +65,7 @@ class MergeRequest < ActiveRecord::Base
     dependent: :delete_all # rubocop:disable Cop/ActiveRecordDependent
 
   has_many :cached_closes_issues, through: :merge_requests_closing_issues, source: :issue
+  has_many :merge_request_pipelines, foreign_key: 'merge_request_id', class_name: 'Ci::Pipeline'
 
   belongs_to :assignee, class_name: "User"
 
@@ -1054,12 +1055,17 @@ class MergeRequest < ActiveRecord::Base
     diverged_commits_count > 0
   end
 
-  def all_pipelines
+  def all_pipelines(shas: all_commit_shas)
     return Ci::Pipeline.none unless source_project
 
     @all_pipelines ||= source_project.pipelines
-      .where(sha: all_commit_shas, ref: source_branch)
-      .order(id: :desc)
+      .where(sha: shas, ref: source_branch)
+      .where(merge_request: [nil, self])
+      .sort_by_merge_request_pipelines
+  end
+
+  def merge_request_pipeline_exists?
+    merge_request_pipelines.exists?(sha: diff_head_sha)
   end
 
   def has_test_reports?
diff --git a/app/serializers/pipeline_entity.rb b/app/serializers/pipeline_entity.rb
index aef838409e0c718da3a1449f500142be6d8ef8c2..477b67101681d857babef7eb972cbc9066faf82d 100644
--- a/app/serializers/pipeline_entity.rb
+++ b/app/serializers/pipeline_entity.rb
@@ -48,6 +48,7 @@ class PipelineEntity < Grape::Entity
 
     expose :tag?, as: :tag
     expose :branch?, as: :branch
+    expose :merge_request?, as: :merge_request
   end
 
   expose :commit, using: CommitEntity
diff --git a/app/services/ci/create_pipeline_service.rb b/app/services/ci/create_pipeline_service.rb
index 290ae10cbd4a96b1476627ccc47674e3cd722f7f..d36cecee8e157091e58880a6e0a3937661846be8 100644
--- a/app/services/ci/create_pipeline_service.rb
+++ b/app/services/ci/create_pipeline_service.rb
@@ -17,7 +17,7 @@ module Ci
                 Gitlab::Ci::Pipeline::Chain::Create,
                 EE::Gitlab::Ci::Pipeline::Chain::Limit::Activity].freeze
 
-    def execute(source, ignore_skip_ci: false, save_on_errors: true, trigger_request: nil, schedule: nil, mirror_update: false, &block)
+    def execute(source, ignore_skip_ci: false, save_on_errors: true, trigger_request: nil, schedule: nil, merge_request: nil, mirror_update: false, &block)
       @pipeline = Ci::Pipeline.new
 
       command = Gitlab::Ci::Pipeline::Chain::Command.new(
@@ -28,6 +28,7 @@ module Ci
         before_sha: params[:before],
         trigger_request: trigger_request,
         schedule: schedule,
+        merge_request: merge_request,
         ignore_skip_ci: ignore_skip_ci,
         save_incompleted: save_on_errors,
         seeds_block: block,
diff --git a/app/services/merge_requests/base_service.rb b/app/services/merge_requests/base_service.rb
index bfde281dd305379bf8bf9488b1f4ae5c1fffca38..2b5472cf898c15596193046c5440cd864d42e297 100644
--- a/app/services/merge_requests/base_service.rb
+++ b/app/services/merge_requests/base_service.rb
@@ -54,6 +54,24 @@ module MergeRequests
         merge_request, merge_request.project, current_user, merge_request.assignee)
     end
 
+    def create_merge_request_pipeline(merge_request, user)
+      return unless Feature.enabled?(:ci_merge_request_pipeline,
+                                     merge_request.source_project,
+                                     default_enabled: true)
+
+      ##
+      # UpdateMergeRequestsWorker could be retried by an exception.
+      # MR pipelines should not be recreated in such case.
+      return if merge_request.merge_request_pipeline_exists?
+
+      Ci::CreatePipelineService
+        .new(merge_request.source_project, user, ref: merge_request.source_branch)
+        .execute(:merge_request,
+                 ignore_skip_ci: true,
+                 save_on_errors: false,
+                 merge_request: merge_request)
+    end
+
     # Returns all origin and fork merge requests from `@project` satisfying passed arguments.
     # rubocop: disable CodeReuse/ActiveRecord
     def merge_requests_for(source_branch, mr_states: [:opened])
diff --git a/app/services/merge_requests/create_service.rb b/app/services/merge_requests/create_service.rb
index 6081a7d1de0c8208c96300a3e7daa098e033fe49..7bb9fa605156dbdf911fb0b41ac17d57fe04cde0 100644
--- a/app/services/merge_requests/create_service.rb
+++ b/app/services/merge_requests/create_service.rb
@@ -25,6 +25,7 @@ module MergeRequests
     def after_create(issuable)
       todo_service.new_merge_request(issuable, current_user)
       issuable.cache_merge_request_closes_issues!(current_user)
+      create_merge_request_pipeline(issuable, current_user)
       update_merge_requests_head_pipeline(issuable)
 
       super
@@ -49,18 +50,14 @@ module MergeRequests
       merge_request.update(head_pipeline_id: pipeline.id) if pipeline
     end
 
-    # rubocop: disable CodeReuse/ActiveRecord
     def head_pipeline_for(merge_request)
       return unless merge_request.source_project
 
       sha = merge_request.source_branch_sha
       return unless sha
 
-      pipelines = merge_request.source_project.pipelines.where(ref: merge_request.source_branch, sha: sha)
-
-      pipelines.order(id: :desc).first
+      merge_request.all_pipelines(shas: sha).first
     end
-    # rubocop: enable CodeReuse/ActiveRecord
 
     def set_projects!
       # @project is used to determine whether the user can set the merge request's
diff --git a/app/services/merge_requests/refresh_service.rb b/app/services/merge_requests/refresh_service.rb
index c7753d52f217cbdef8a8520890713f6eb3b227fd..4792e01579a6329e35fbdd29030ab6ce51aea1fb 100644
--- a/app/services/merge_requests/refresh_service.rb
+++ b/app/services/merge_requests/refresh_service.rb
@@ -92,6 +92,7 @@ module MergeRequests
         end
 
         merge_request.mark_as_unchecked
+        create_merge_request_pipeline(merge_request, current_user)
         UpdateHeadPipelineForMergeRequestWorker.perform_async(merge_request.id)
       end
 
diff --git a/app/workers/update_head_pipeline_for_merge_request_worker.rb b/app/workers/update_head_pipeline_for_merge_request_worker.rb
index 9ce51662969f330454bbbf90612ca559c3d6847a..e8494ffa002b78c0ad5b3bf91c49c5d4dee4b07c 100644
--- a/app/workers/update_head_pipeline_for_merge_request_worker.rb
+++ b/app/workers/update_head_pipeline_for_merge_request_worker.rb
@@ -6,10 +6,11 @@ class UpdateHeadPipelineForMergeRequestWorker
 
   queue_namespace :pipeline_processing
 
-  # rubocop: disable CodeReuse/ActiveRecord
   def perform(merge_request_id)
     merge_request = MergeRequest.find(merge_request_id)
-    pipeline = Ci::Pipeline.where(project: merge_request.source_project, ref: merge_request.source_branch).last
+
+    sha = merge_request.diff_head_sha
+    pipeline = merge_request.all_pipelines(shas: sha).first
 
     return unless pipeline && pipeline.latest?
 
@@ -21,7 +22,6 @@ class UpdateHeadPipelineForMergeRequestWorker
 
     merge_request.update_attribute(:head_pipeline_id, pipeline.id)
   end
-  # rubocop: enable CodeReuse/ActiveRecord
 
   def log_error_message_for(merge_request)
     Rails.logger.error(
diff --git a/changelogs/unreleased/mr-pipelines-2.yml b/changelogs/unreleased/mr-pipelines-2.yml
new file mode 100644
index 0000000000000000000000000000000000000000..683c626c3ced2b937108d4217b02d0b54f5c2606
--- /dev/null
+++ b/changelogs/unreleased/mr-pipelines-2.yml
@@ -0,0 +1,5 @@
+---
+title: Merge request pipelines
+merge_request: 23217
+author:
+type: added
diff --git a/db/migrate/20181119081539_add_merge_request_id_to_ci_pipelines.rb b/db/migrate/20181119081539_add_merge_request_id_to_ci_pipelines.rb
new file mode 100644
index 0000000000000000000000000000000000000000..65476109c61e0eabd304680ccdffcf73d6c19069
--- /dev/null
+++ b/db/migrate/20181119081539_add_merge_request_id_to_ci_pipelines.rb
@@ -0,0 +1,13 @@
+# frozen_string_literal: true
+
+class AddMergeRequestIdToCiPipelines < ActiveRecord::Migration
+  DOWNTIME = false
+
+  def up
+    add_column :ci_pipelines, :merge_request_id, :integer
+  end
+
+  def down
+    remove_column :ci_pipelines, :merge_request_id, :integer
+  end
+end
diff --git a/db/migrate/20181120091639_add_foreign_key_to_ci_pipelines_merge_requests.rb b/db/migrate/20181120091639_add_foreign_key_to_ci_pipelines_merge_requests.rb
new file mode 100644
index 0000000000000000000000000000000000000000..c2b5b23927909848ee6b5e37c4cf3ff1b5484918
--- /dev/null
+++ b/db/migrate/20181120091639_add_foreign_key_to_ci_pipelines_merge_requests.rb
@@ -0,0 +1,22 @@
+# frozen_string_literal: true
+
+class AddForeignKeyToCiPipelinesMergeRequests < ActiveRecord::Migration
+  include Gitlab::Database::MigrationHelpers
+
+  DOWNTIME = false
+
+  disable_ddl_transaction!
+
+  def up
+    add_concurrent_index :ci_pipelines, :merge_request_id
+    add_concurrent_foreign_key :ci_pipelines, :merge_requests, column: :merge_request_id, on_delete: :cascade
+  end
+
+  def down
+    if foreign_key_exists?(:ci_pipelines, :merge_requests, column: :merge_request_id)
+      remove_foreign_key :ci_pipelines, :merge_requests
+    end
+
+    remove_concurrent_index :ci_pipelines, :merge_request_id
+  end
+end
diff --git a/db/schema.rb b/db/schema.rb
index f418a4ac10f46d85e605fda722abcd53c99f3660..416a6420468f9a4a2e461274bf528de40a413a0a 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -577,7 +577,9 @@ ActiveRecord::Schema.define(version: 20181126153547) do
     t.boolean "protected"
     t.integer "failure_reason"
     t.integer "iid"
+    t.integer "merge_request_id"
     t.index ["auto_canceled_by_id"], name: "index_ci_pipelines_on_auto_canceled_by_id", using: :btree
+    t.index ["merge_request_id"], name: "index_ci_pipelines_on_merge_request_id", using: :btree
     t.index ["pipeline_schedule_id"], name: "index_ci_pipelines_on_pipeline_schedule_id", using: :btree
     t.index ["project_id", "iid"], name: "index_ci_pipelines_on_project_id_and_iid", unique: true, where: "(iid IS NOT NULL)", using: :btree
     t.index ["project_id", "ref", "status", "id"], name: "index_ci_pipelines_on_project_id_and_ref_and_status_and_id", using: :btree
@@ -3124,6 +3126,7 @@ ActiveRecord::Schema.define(version: 20181126153547) do
   add_foreign_key "ci_pipeline_variables", "ci_pipelines", column: "pipeline_id", name: "fk_f29c5f4380", on_delete: :cascade
   add_foreign_key "ci_pipelines", "ci_pipeline_schedules", column: "pipeline_schedule_id", name: "fk_3d34ab2e06", on_delete: :nullify
   add_foreign_key "ci_pipelines", "ci_pipelines", column: "auto_canceled_by_id", name: "fk_262d4c2d19", on_delete: :nullify
+  add_foreign_key "ci_pipelines", "merge_requests", name: "fk_a23be95014", on_delete: :cascade
   add_foreign_key "ci_pipelines", "projects", name: "fk_86635dbd80", on_delete: :cascade
   add_foreign_key "ci_runner_namespaces", "ci_runners", column: "runner_id", on_delete: :cascade
   add_foreign_key "ci_runner_namespaces", "namespaces", on_delete: :cascade
diff --git a/lib/gitlab/ci/pipeline/chain/build.rb b/lib/gitlab/ci/pipeline/chain/build.rb
index b445a872b3d483e3d1d01b72f55b0df107464f00..d33d1edfe35520f65eec1fc2ab107854c0421ea7 100644
--- a/lib/gitlab/ci/pipeline/chain/build.rb
+++ b/lib/gitlab/ci/pipeline/chain/build.rb
@@ -16,6 +16,7 @@ module Gitlab
               trigger_requests: Array(@command.trigger_request),
               user: @command.current_user,
               pipeline_schedule: @command.schedule,
+              merge_request: @command.merge_request,
               protected: @command.protected_ref?,
               variables_attributes: Array(@command.variables_attributes)
             )
diff --git a/lib/gitlab/ci/pipeline/chain/command.rb b/lib/gitlab/ci/pipeline/chain/command.rb
index befda6576fdd320f19cff59caa5197a6cb3642c2..72d1619e900fe2cdf59441f5e005f19d4a031646 100644
--- a/lib/gitlab/ci/pipeline/chain/command.rb
+++ b/lib/gitlab/ci/pipeline/chain/command.rb
@@ -8,7 +8,7 @@ module Gitlab
         Command = Struct.new(
           :source, :project, :current_user,
           :origin_ref, :checkout_sha, :after_sha, :before_sha,
-          :trigger_request, :schedule,
+          :trigger_request, :schedule, :merge_request,
           :ignore_skip_ci, :save_incompleted,
           :seeds_block, :variables_attributes,
 
diff --git a/spec/features/merge_request/user_sees_merge_request_pipelines_spec.rb b/spec/features/merge_request/user_sees_merge_request_pipelines_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..ae1313cf1170abe790fda58068c7c0daf97e637c
--- /dev/null
+++ b/spec/features/merge_request/user_sees_merge_request_pipelines_spec.rb
@@ -0,0 +1,333 @@
+# frozen_string_literal: true
+
+require 'rails_helper'
+
+describe 'Merge request > User sees merge request pipelines', :js do
+  include ProjectForksHelper
+  include TestReportsHelper
+
+  let(:project) { create(:project, :public, :repository) }
+  let(:user) { project.creator }
+
+  let(:config) do
+    {
+      build: {
+        script: 'build'
+      },
+      test: {
+        script: 'test',
+        only: ['merge_requests']
+      },
+      deploy: {
+        script: 'deploy',
+        except: ['merge_requests']
+      }
+    }
+  end
+
+  before do
+    stub_application_setting(auto_devops_enabled: false)
+    stub_feature_flags(ci_merge_request_pipeline: true)
+    stub_ci_pipeline_yaml_file(YAML.dump(config))
+    project.add_maintainer(user)
+    sign_in(user)
+  end
+
+  context 'when a user created a merge request in the parent project' do
+    let(:merge_request) do
+      create(:merge_request,
+              source_project: project,
+              target_project: project,
+              source_branch: 'feature',
+              target_branch: 'master')
+    end
+
+    let!(:push_pipeline) do
+      Ci::CreatePipelineService.new(project, user, ref: 'feature')
+                                .execute(:push)
+    end
+
+    let!(:merge_request_pipeline) do
+      Ci::CreatePipelineService.new(project, user, ref: 'feature')
+                                .execute(:merge_request, merge_request: merge_request)
+    end
+
+    before do
+      visit project_merge_request_path(project, merge_request)
+
+      page.within('.merge-request-tabs') do
+        click_link('Pipelines')
+      end
+    end
+
+    it 'sees branch pipelines and merge request pipelines in correct order' do
+      page.within('.ci-table') do
+        expect(page).to have_selector('.ci-pending', count: 2)
+        expect(first('.js-pipeline-url-link')).to have_content("##{merge_request_pipeline.id}")
+      end
+    end
+
+    it 'sees the latest merge request pipeline as the head pipeline' do
+      page.within('.ci-widget-content') do
+        expect(page).to have_content("##{merge_request_pipeline.id}")
+      end
+    end
+
+    context 'when a user updated a merge request in the parent project' do
+      let!(:push_pipeline_2) do
+        Ci::CreatePipelineService.new(project, user, ref: 'feature')
+                                  .execute(:push)
+      end
+
+      let!(:merge_request_pipeline_2) do
+        Ci::CreatePipelineService.new(project, user, ref: 'feature')
+                                  .execute(:merge_request, merge_request: merge_request)
+      end
+
+      before do
+        visit project_merge_request_path(project, merge_request)
+
+        page.within('.merge-request-tabs') do
+          click_link('Pipelines')
+        end
+      end
+
+      it 'sees branch pipelines and merge request pipelines in correct order' do
+        page.within('.ci-table') do
+          expect(page).to have_selector('.ci-pending', count: 4)
+
+          expect(all('.js-pipeline-url-link')[0])
+            .to have_content("##{merge_request_pipeline_2.id}")
+
+          expect(all('.js-pipeline-url-link')[1])
+            .to have_content("##{merge_request_pipeline.id}")
+
+          expect(all('.js-pipeline-url-link')[2])
+            .to have_content("##{push_pipeline_2.id}")
+
+          expect(all('.js-pipeline-url-link')[3])
+            .to have_content("##{push_pipeline.id}")
+        end
+      end
+
+      it 'sees the latest merge request pipeline as the head pipeline' do
+        page.within('.ci-widget-content') do
+          expect(page).to have_content("##{merge_request_pipeline_2.id}")
+        end
+      end
+    end
+
+    context 'when a user merges a merge request in the parent project' do
+      before do
+        click_button 'Merge when pipeline succeeds'
+
+        wait_for_requests
+      end
+
+      context 'when merge request pipeline is pending' do
+        it 'waits the head pipeline' do
+          expect(page).to have_content('to be merged automatically when the pipeline succeeds')
+          expect(page).to have_link('Cancel automatic merge')
+        end
+      end
+
+      context 'when merge request pipeline succeeds' do
+        before do
+          merge_request_pipeline.succeed!
+
+          wait_for_requests
+        end
+
+        it 'merges the merge request' do
+          expect(page).to have_content('Merged by')
+          expect(page).to have_link('Revert')
+        end
+      end
+
+      context 'when branch pipeline succeeds' do
+        before do
+          push_pipeline.succeed!
+
+          wait_for_requests
+        end
+
+        it 'waits the head pipeline' do
+          expect(page).to have_content('to be merged automatically when the pipeline succeeds')
+          expect(page).to have_link('Cancel automatic merge')
+        end
+      end
+    end
+
+    context 'when there are no `merge_requests` keyword in .gitlab-ci.yml' do
+      let(:config) do
+        {
+          build: {
+            script: 'build'
+          },
+          test: {
+            script: 'test'
+          },
+          deploy: {
+            script: 'deploy'
+          }
+        }
+      end
+
+      it 'sees a branch pipeline in pipeline tab' do
+        page.within('.ci-table') do
+          expect(page).to have_selector('.ci-pending', count: 1)
+          expect(first('.js-pipeline-url-link')).to have_content("##{push_pipeline.id}")
+        end
+      end
+
+      it 'sees the latest branch pipeline as the head pipeline' do
+        page.within('.ci-widget-content') do
+          expect(page).to have_content("##{push_pipeline.id}")
+        end
+      end
+    end
+  end
+
+  context 'when a user created a merge request from a forked project to the parent project' do
+    let(:merge_request) do
+      create(:merge_request,
+              source_project: forked_project,
+              target_project: project,
+              source_branch: 'feature',
+              target_branch: 'master')
+    end
+
+    let!(:push_pipeline) do
+      Ci::CreatePipelineService.new(forked_project, user2, ref: 'feature')
+                                .execute(:push)
+    end
+
+    let!(:merge_request_pipeline) do
+      Ci::CreatePipelineService.new(forked_project, user2, ref: 'feature')
+                                .execute(:merge_request, merge_request: merge_request)
+    end
+
+    let(:forked_project) { fork_project(project, user2, repository: true) }
+    let(:user2) { create(:user) }
+
+    before do
+      forked_project.add_maintainer(user2)
+
+      visit project_merge_request_path(project, merge_request)
+
+      page.within('.merge-request-tabs') do
+        click_link('Pipelines')
+      end
+    end
+
+    it 'sees branch pipelines and merge request pipelines in correct order' do
+      page.within('.ci-table') do
+        expect(page).to have_selector('.ci-pending', count: 2)
+        expect(first('.js-pipeline-url-link')).to have_content("##{merge_request_pipeline.id}")
+      end
+    end
+
+    it 'sees the latest merge request pipeline as the head pipeline' do
+      page.within('.ci-widget-content') do
+        expect(page).to have_content("##{merge_request_pipeline.id}")
+      end
+    end
+
+    it 'sees pipeline list in forked project' do
+      visit project_pipelines_path(forked_project)
+
+      expect(page).to have_selector('.ci-pending', count: 2)
+    end
+
+    context 'when a user updated a merge request from a forked project to the parent project' do
+      let!(:push_pipeline_2) do
+        Ci::CreatePipelineService.new(forked_project, user2, ref: 'feature')
+                                  .execute(:push)
+      end
+
+      let!(:merge_request_pipeline_2) do
+        Ci::CreatePipelineService.new(forked_project, user2, ref: 'feature')
+                                  .execute(:merge_request, merge_request: merge_request)
+      end
+
+      before do
+        visit project_merge_request_path(project, merge_request)
+
+        page.within('.merge-request-tabs') do
+          click_link('Pipelines')
+        end
+      end
+
+      it 'sees branch pipelines and merge request pipelines in correct order' do
+        page.within('.ci-table') do
+          expect(page).to have_selector('.ci-pending', count: 4)
+
+          expect(all('.js-pipeline-url-link')[0])
+            .to have_content("##{merge_request_pipeline_2.id}")
+
+          expect(all('.js-pipeline-url-link')[1])
+            .to have_content("##{merge_request_pipeline.id}")
+
+          expect(all('.js-pipeline-url-link')[2])
+            .to have_content("##{push_pipeline_2.id}")
+
+          expect(all('.js-pipeline-url-link')[3])
+            .to have_content("##{push_pipeline.id}")
+        end
+      end
+
+      it 'sees the latest merge request pipeline as the head pipeline' do
+        page.within('.ci-widget-content') do
+          expect(page).to have_content("##{merge_request_pipeline_2.id}")
+        end
+      end
+
+      it 'sees pipeline list in forked project' do
+        visit project_pipelines_path(forked_project)
+
+        expect(page).to have_selector('.ci-pending', count: 4)
+      end
+    end
+
+    context 'when a user merges a merge request from a forked project to the parent project' do
+      before do
+        click_button 'Merge when pipeline succeeds'
+
+        wait_for_requests
+      end
+
+      context 'when merge request pipeline is pending' do
+        it 'waits the head pipeline' do
+          expect(page).to have_content('to be merged automatically when the pipeline succeeds')
+          expect(page).to have_link('Cancel automatic merge')
+        end
+      end
+
+      context 'when merge request pipeline succeeds' do
+        before do
+          merge_request_pipeline.succeed!
+
+          wait_for_requests
+        end
+
+        it 'merges the merge request' do
+          expect(page).to have_content('Merged by')
+          expect(page).to have_link('Revert')
+        end
+      end
+
+      context 'when branch pipeline succeeds' do
+        before do
+          push_pipeline.succeed!
+
+          wait_for_requests
+        end
+
+        it 'waits the head pipeline' do
+          expect(page).to have_content('to be merged automatically when the pipeline succeeds')
+          expect(page).to have_link('Cancel automatic merge')
+        end
+      end
+    end
+  end
+end
diff --git a/spec/lib/gitlab/ci/pipeline/chain/build_spec.rb b/spec/lib/gitlab/ci/pipeline/chain/build_spec.rb
index 85d73e5c3826b0be377242dd674b364d422b5617..fab071405dfd9c91274d37aa38e82601e4323c1f 100644
--- a/spec/lib/gitlab/ci/pipeline/chain/build_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/chain/build_spec.rb
@@ -18,6 +18,7 @@ describe Gitlab::Ci::Pipeline::Chain::Build do
       before_sha: nil,
       trigger_request: nil,
       schedule: nil,
+      merge_request: nil,
       project: project,
       current_user: user,
       variables_attributes: variables_attributes)
@@ -76,6 +77,7 @@ describe Gitlab::Ci::Pipeline::Chain::Build do
         before_sha: nil,
         trigger_request: nil,
         schedule: nil,
+        merge_request: nil,
         project: project,
         current_user: user)
     end
@@ -90,4 +92,31 @@ describe Gitlab::Ci::Pipeline::Chain::Build do
       expect(pipeline).to be_tag
     end
   end
+
+  context 'when pipeline is running for a merge request' do
+    let(:command) do
+      Gitlab::Ci::Pipeline::Chain::Command.new(
+        source: :merge_request,
+        origin_ref: 'feature',
+        checkout_sha: project.commit.id,
+        after_sha: nil,
+        before_sha: nil,
+        trigger_request: nil,
+        schedule: nil,
+        merge_request: merge_request,
+        project: project,
+        current_user: user)
+    end
+
+    let(:merge_request) { build(:merge_request, target_project: project) }
+
+    before do
+      step.perform!
+    end
+
+    it 'correctly indicated that this is a merge request pipeline' do
+      expect(pipeline).to be_merge_request
+      expect(pipeline.merge_request).to eq(merge_request)
+    end
+  end
 end
diff --git a/spec/lib/gitlab/ci/pipeline/chain/validate/config_spec.rb b/spec/lib/gitlab/ci/pipeline/chain/validate/config_spec.rb
index a8dc5356413e6f9d91148ed22c1e17ce1e4811f9..053bc421649f0f03db9d5c5ecd4a4455826e0497 100644
--- a/spec/lib/gitlab/ci/pipeline/chain/validate/config_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/chain/validate/config_spec.rb
@@ -106,4 +106,34 @@ describe Gitlab::Ci::Pipeline::Chain::Validate::Config do
       expect(step.break?).to be false
     end
   end
+
+  context 'when pipeline source is merge request' do
+    before do
+      stub_ci_pipeline_yaml_file(YAML.dump(config))
+    end
+
+    let(:pipeline) { build_stubbed(:ci_pipeline, project: project) }
+
+    let(:merge_request_pipeline) do
+      build(:ci_pipeline, source: :merge_request, project: project)
+    end
+
+    let(:chain) { described_class.new(merge_request_pipeline, command).tap(&:perform!) }
+
+    context "when config contains 'merge_requests' keyword" do
+      let(:config) { { rspec: { script: 'echo', only: ['merge_requests'] } } }
+
+      it 'does not break the chain' do
+        expect(chain).not_to be_break
+      end
+    end
+
+    context "when config contains 'merge_request' keyword" do
+      let(:config) { { rspec: { script: 'echo', only: ['merge_request'] } } }
+
+      it 'does not break the chain' do
+        expect(chain).not_to be_break
+      end
+    end
+  end
 end
diff --git a/spec/lib/gitlab/import_export/all_models.yml b/spec/lib/gitlab/import_export/all_models.yml
index d3c4fcebddd011fa96d1c48283f8150ad25b04a3..318eed59293c51815934005e73e4daac0827c3cf 100644
--- a/spec/lib/gitlab/import_export/all_models.yml
+++ b/spec/lib/gitlab/import_export/all_models.yml
@@ -94,6 +94,7 @@ merge_requests:
 - timelogs
 - head_pipeline
 - latest_merge_request_diff
+- merge_request_pipelines
 merge_request_diff:
 - merge_request
 - merge_request_diff_commits
@@ -121,6 +122,7 @@ pipelines:
 - artifacts
 - pipeline_schedule
 - merge_requests
+- merge_request
 - deployments
 - environments
 pipeline_variables:
diff --git a/spec/lib/gitlab/import_export/safe_model_attributes.yml b/spec/lib/gitlab/import_export/safe_model_attributes.yml
index 17eac351f8270e466f59b4e243cb3ee28cec0e5c..a2c86c309f141aaafe0c3e7dcf71bf421987650f 100644
--- a/spec/lib/gitlab/import_export/safe_model_attributes.yml
+++ b/spec/lib/gitlab/import_export/safe_model_attributes.yml
@@ -246,6 +246,7 @@ Ci::Pipeline:
 - failure_reason
 - protected
 - iid
+- merge_request_id
 Ci::Stage:
 - id
 - name
diff --git a/spec/models/ci/pipeline_spec.rb b/spec/models/ci/pipeline_spec.rb
index 0f5452a1203c18f70d02ddfbda706e21d50d3127..c8de0a59f2ecd6b3b1a6414094faf970fe96fa03 100644
--- a/spec/models/ci/pipeline_spec.rb
+++ b/spec/models/ci/pipeline_spec.rb
@@ -14,6 +14,7 @@ describe Ci::Pipeline, :mailer do
   it { is_expected.to belong_to(:user) }
   it { is_expected.to belong_to(:auto_canceled_by) }
   it { is_expected.to belong_to(:pipeline_schedule) }
+  it { is_expected.to belong_to(:merge_request) }
 
   it { is_expected.to have_many(:statuses) }
   it { is_expected.to have_many(:trigger_requests) }
@@ -41,6 +42,128 @@ describe Ci::Pipeline, :mailer do
     end
   end
 
+  describe '.sort_by_merge_request_pipelines' do
+    subject { described_class.sort_by_merge_request_pipelines }
+
+    context 'when branch pipelines exist' do
+      let!(:branch_pipeline_1) { create(:ci_pipeline, source: :push) }
+      let!(:branch_pipeline_2) { create(:ci_pipeline, source: :push) }
+
+      it 'returns pipelines order by id' do
+        expect(subject).to eq([branch_pipeline_2,
+                               branch_pipeline_1])
+      end
+    end
+
+    context 'when merge request pipelines exist' do
+      let!(:merge_request_pipeline_1) do
+        create(:ci_pipeline, source: :merge_request, merge_request: merge_request)
+      end
+
+      let!(:merge_request_pipeline_2) do
+        create(:ci_pipeline, source: :merge_request, merge_request: merge_request)
+      end
+
+      let(:merge_request) do
+        create(:merge_request,
+               source_project: project,
+               source_branch: 'feature',
+               target_project: project,
+               target_branch: 'master')
+      end
+
+      it 'returns pipelines order by id' do
+        expect(subject).to eq([merge_request_pipeline_2,
+                               merge_request_pipeline_1])
+      end
+    end
+
+    context 'when both branch pipeline and merge request pipeline exist' do
+      let!(:branch_pipeline_1) { create(:ci_pipeline, source: :push) }
+      let!(:branch_pipeline_2) { create(:ci_pipeline, source: :push) }
+
+      let!(:merge_request_pipeline_1) do
+        create(:ci_pipeline, source: :merge_request, merge_request: merge_request)
+      end
+
+      let!(:merge_request_pipeline_2) do
+        create(:ci_pipeline, source: :merge_request, merge_request: merge_request)
+      end
+
+      let(:merge_request) do
+        create(:merge_request,
+               source_project: project,
+               source_branch: 'feature',
+               target_project: project,
+               target_branch: 'master')
+      end
+
+      it 'returns merge request pipeline first' do
+        expect(subject).to eq([merge_request_pipeline_2,
+                               merge_request_pipeline_1,
+                               branch_pipeline_2,
+                               branch_pipeline_1])
+      end
+    end
+  end
+
+  describe '.merge_request' do
+    subject { described_class.merge_request }
+
+    context 'when there is a merge request pipeline' do
+      let!(:pipeline) { create(:ci_pipeline, source: :merge_request, merge_request: merge_request) }
+      let(:merge_request) { create(:merge_request) }
+
+      it 'returns merge request pipeline first' do
+        expect(subject).to eq([pipeline])
+      end
+    end
+
+    context 'when there are no merge request pipelines' do
+      let!(:pipeline) { create(:ci_pipeline, source: :push) }
+
+      it 'returns empty array' do
+        expect(subject).to be_empty
+      end
+    end
+  end
+
+  describe 'Validations for merge request pipelines' do
+    let(:pipeline) { build(:ci_pipeline, source: source, merge_request: merge_request) }
+
+    context 'when source is merge request' do
+      let(:source) { :merge_request }
+
+      context 'when merge request is specified' do
+        let(:merge_request) { create(:merge_request, source_project: project, source_branch: 'feature', target_project: project, target_branch: 'master') }
+
+        it { expect(pipeline).to be_valid }
+      end
+
+      context 'when merge request is empty' do
+        let(:merge_request) { nil }
+
+        it { expect(pipeline).not_to be_valid }
+      end
+    end
+
+    context 'when source is web' do
+      let(:source) { :web }
+
+      context 'when merge request is specified' do
+        let(:merge_request) { create(:merge_request, source_project: project, source_branch: 'feature', target_project: project, target_branch: 'master') }
+
+        it { expect(pipeline).not_to be_valid }
+      end
+
+      context 'when merge request is empty' do
+        let(:merge_request) { nil }
+
+        it { expect(pipeline).to be_valid }
+      end
+    end
+  end
+
   describe 'modules' do
     it_behaves_like 'AtomicInternalId', validate_presence: false do
       let(:internal_id_attribute) { :iid }
@@ -769,27 +892,85 @@ describe Ci::Pipeline, :mailer do
   describe '#branch?' do
     subject { pipeline.branch? }
 
-    context 'is not a tag' do
+    context 'when ref is not a tag' do
       before do
         pipeline.tag = false
       end
 
-      it 'return true when tag is set to false' do
+      it 'return true' do
         is_expected.to be_truthy
       end
+
+      context 'when source is merge request' do
+        let(:pipeline) do
+          create(:ci_pipeline, source: :merge_request, merge_request: merge_request)
+        end
+
+        let(:merge_request) do
+          create(:merge_request,
+                 source_project: project,
+                 source_branch: 'feature',
+                 target_project: project,
+                 target_branch: 'master')
+        end
+
+        it 'returns false' do
+          is_expected.to be_falsey
+        end
+      end
     end
 
-    context 'is not a tag' do
+    context 'when ref is a tag' do
       before do
         pipeline.tag = true
       end
 
-      it 'return false when tag is set to true' do
+      it 'return false' do
         is_expected.to be_falsey
       end
     end
   end
 
+  describe '#git_ref' do
+    subject { pipeline.send(:git_ref) }
+
+    context 'when ref is branch' do
+      let(:pipeline) { create(:ci_pipeline, tag: false) }
+
+      it 'returns branch ref' do
+        is_expected.to eq(Gitlab::Git::BRANCH_REF_PREFIX + pipeline.ref.to_s)
+      end
+    end
+
+    context 'when ref is tag' do
+      let(:pipeline) { create(:ci_pipeline, tag: true) }
+
+      it 'returns branch ref' do
+        is_expected.to eq(Gitlab::Git::TAG_REF_PREFIX + pipeline.ref.to_s)
+      end
+    end
+
+    context 'when ref is merge request' do
+      let(:pipeline) do
+        create(:ci_pipeline,
+               source: :merge_request,
+               merge_request: merge_request)
+      end
+
+      let(:merge_request) do
+        create(:merge_request,
+               source_project: project,
+               source_branch: 'feature',
+               target_project: project,
+               target_branch: 'master')
+      end
+
+      it 'returns branch ref' do
+        is_expected.to eq(Gitlab::Git::BRANCH_REF_PREFIX + pipeline.ref.to_s)
+      end
+    end
+  end
+
   describe 'ref_exists?' do
     context 'when repository exists' do
       using RSpec::Parameterized::TableSyntax
@@ -1881,6 +2062,55 @@ describe Ci::Pipeline, :mailer do
 
       expect(pipeline.all_merge_requests).to be_empty
     end
+
+    context 'when there is a merge request pipeline' do
+      let(:source_branch) { 'feature' }
+      let(:target_branch) { 'master' }
+
+      let!(:pipeline) do
+        create(:ci_pipeline,
+               source: :merge_request,
+               project: project,
+               ref: source_branch,
+               merge_request: merge_request)
+      end
+
+      let(:merge_request) do
+        create(:merge_request,
+               source_project: project,
+               source_branch: source_branch,
+               target_project: project,
+               target_branch: target_branch)
+      end
+
+      it 'returns an associated merge request' do
+        expect(pipeline.all_merge_requests).to eq([merge_request])
+      end
+
+      context 'when there is another merge request pipeline that targets a different branch' do
+        let(:target_branch_2) { 'merge-test' }
+
+        let!(:pipeline_2) do
+          create(:ci_pipeline,
+                 source: :merge_request,
+                 project: project,
+                 ref: source_branch,
+                 merge_request: merge_request_2)
+        end
+
+        let(:merge_request_2) do
+          create(:merge_request,
+                 source_project: project,
+                 source_branch: source_branch,
+                 target_project: project,
+                 target_branch: target_branch_2)
+        end
+
+        it 'does not return an associated merge request' do
+          expect(pipeline.all_merge_requests).not_to include(merge_request_2)
+        end
+      end
+    end
   end
 
   describe '#stuck?' do
diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb
index 78872324a99ef937ba642548f5ecda76182f095a..8bbb1b1411f0fee2153667883c24d556469df51e 100644
--- a/spec/models/merge_request_spec.rb
+++ b/spec/models/merge_request_spec.rb
@@ -1337,6 +1337,119 @@ describe MergeRequest do
         expect(subject.all_pipelines).to contain_exactly(pipeline)
       end
     end
+
+    context 'when pipelines exist for the branch and merge request' do
+      let(:source_ref) { 'feature' }
+      let(:target_ref) { 'master' }
+
+      let!(:branch_pipeline) do
+        create(:ci_pipeline,
+               source: :push,
+               project: project,
+               ref: source_ref,
+               sha: shas.second)
+      end
+
+      let!(:merge_request_pipeline) do
+        create(:ci_pipeline,
+               source: :merge_request,
+               project: project,
+               ref: source_ref,
+               sha: shas.second,
+               merge_request: merge_request)
+      end
+
+      let(:merge_request) do
+        create(:merge_request,
+               source_project: project,
+               source_branch: source_ref,
+               target_project: project,
+               target_branch: target_ref)
+      end
+
+      let(:project) { create(:project, :repository) }
+      let(:shas) { project.repository.commits(source_ref, limit: 2).map(&:id) }
+
+      before do
+        allow(merge_request).to receive(:all_commit_shas) { shas }
+      end
+
+      it 'returns merge request pipeline first' do
+        expect(merge_request.all_pipelines)
+          .to eq([merge_request_pipeline,
+                  branch_pipeline])
+      end
+
+      context 'when there are a branch pipeline and a merge request pipeline' do
+        let!(:branch_pipeline_2) do
+          create(:ci_pipeline,
+                 source: :push,
+                 project: project,
+                 ref: source_ref,
+                 sha: shas.first)
+        end
+
+        let!(:merge_request_pipeline_2) do
+          create(:ci_pipeline,
+                 source: :merge_request,
+                 project: project,
+                 ref: source_ref,
+                 sha: shas.first,
+                 merge_request: merge_request)
+        end
+
+        it 'returns merge request pipelines first' do
+          expect(merge_request.all_pipelines)
+            .to eq([merge_request_pipeline_2,
+                    merge_request_pipeline,
+                    branch_pipeline_2,
+                    branch_pipeline])
+        end
+      end
+
+      context 'when there are multiple merge request pipelines from the same branch' do
+        let!(:branch_pipeline_2) do
+          create(:ci_pipeline,
+                 source: :push,
+                 project: project,
+                 ref: source_ref,
+                 sha: shas.first)
+        end
+
+        let!(:merge_request_pipeline_2) do
+          create(:ci_pipeline,
+                 source: :merge_request,
+                 project: project,
+                 ref: source_ref,
+                 sha: shas.first,
+                 merge_request: merge_request_2)
+        end
+
+        let(:merge_request_2) do
+          create(:merge_request,
+                 source_project: project,
+                 source_branch: source_ref,
+                 target_project: project,
+                 target_branch: 'stable')
+        end
+
+        before do
+          allow(merge_request_2).to receive(:all_commit_shas) { shas }
+        end
+
+        it 'returns only related merge request pipelines' do
+          expect(merge_request.all_pipelines)
+            .to eq([merge_request_pipeline,
+                    branch_pipeline_2,
+                    branch_pipeline])
+
+          expect(merge_request_2.all_pipelines)
+            .to eq([merge_request_pipeline_2,
+                    branch_pipeline_2,
+                    branch_pipeline])
+        end
+      end
+    end
   end
 
   describe '#has_test_reports?' do
diff --git a/spec/services/ci/create_pipeline_service_spec.rb b/spec/services/ci/create_pipeline_service_spec.rb
index a4582d1bc6416ae1877d4ae1040042bf6830607f..7bc990f05c2b276caba1b2239a0acd7703487362 100644
--- a/spec/services/ci/create_pipeline_service_spec.rb
+++ b/spec/services/ci/create_pipeline_service_spec.rb
@@ -18,7 +18,8 @@ describe Ci::CreatePipelineService do
       message: 'Message',
       ref: ref_name,
       trigger_request: nil,
-      variables_attributes: nil)
+      variables_attributes: nil,
+      merge_request: nil)
       params = { ref: ref,
                  before: '00000000',
                  after: after,
@@ -26,7 +27,7 @@ describe Ci::CreatePipelineService do
                  variables_attributes: variables_attributes }
 
       described_class.new(project, user, params).execute(
-        source, trigger_request: trigger_request)
+        source, trigger_request: trigger_request, merge_request: merge_request)
     end
 
     context 'valid params' do
@@ -60,10 +61,10 @@ describe Ci::CreatePipelineService do
 
       context 'when merge requests already exist for this source branch' do
         let(:merge_request_1) do
-          create(:merge_request, source_branch: 'master', target_branch: "branch_1", source_project: project)
+          create(:merge_request, source_branch: 'feature', target_branch: "master", source_project: project)
         end
         let(:merge_request_2) do
-          create(:merge_request, source_branch: 'master', target_branch: "branch_2", source_project: project)
+          create(:merge_request, source_branch: 'feature', target_branch: "v1.1.0", source_project: project)
         end
 
         context 'when related merge request is already merged' do
@@ -83,7 +84,7 @@ describe Ci::CreatePipelineService do
             merge_request_1
             merge_request_2
 
-            head_pipeline = execute_service
+            head_pipeline = execute_service(ref: 'feature', after: nil)
 
             expect(merge_request_1.reload.head_pipeline).to eq(head_pipeline)
             expect(merge_request_2.reload.head_pipeline).to eq(head_pipeline)
@@ -123,12 +124,12 @@ describe Ci::CreatePipelineService do
           let!(:target_project) { create(:project, :repository) }
 
           it 'updates head pipeline for merge request' do
-            merge_request = create(:merge_request, source_branch: 'master',
-                                                   target_branch: "branch_1",
+            merge_request = create(:merge_request, source_branch: 'feature',
+                                                   target_branch: "master",
                                                    source_project: project,
                                                    target_project: target_project)
 
-            head_pipeline = execute_service
+            head_pipeline = execute_service(ref: 'feature', after: nil)
 
             expect(merge_request.reload.head_pipeline).to eq(head_pipeline)
           end
@@ -656,6 +657,212 @@ describe Ci::CreatePipelineService do
         end
       end
     end
+
+    describe 'Merge request pipelines' do
+      let(:pipeline) do
+        execute_service(source: source, merge_request: merge_request, ref: ref_name)
+      end
+
+      before do
+        stub_ci_pipeline_yaml_file(YAML.dump(config))
+      end
+
+      let(:ref_name) { 'feature' }
+
+      context 'when source is merge request' do
+        let(:source) { :merge_request }
+
+        context "when config has merge_requests keywords" do
+          let(:config) do
+            {
+              build: {
+                stage: 'build',
+                script: 'echo'
+              },
+              test: {
+                stage: 'test',
+                script: 'echo',
+                only: ['merge_requests']
+              },
+              pages: {
+                stage: 'deploy',
+                script: 'echo',
+                except: ['merge_requests']
+              }
+            }
+          end
+
+          context 'when merge request is specified' do
+            let(:merge_request) do
+              create(:merge_request,
+                source_project: project,
+                source_branch: ref_name,
+                target_project: project,
+                target_branch: 'master')
+            end
+
+            it 'creates a merge request pipeline' do
+              expect(pipeline).to be_persisted
+              expect(pipeline).to be_merge_request
+              expect(pipeline.merge_request).to eq(merge_request)
+              expect(pipeline.builds.order(:stage_id).map(&:name)).to eq(%w[test])
+            end
+
+            context 'when ref is tag' do
+              let(:ref_name) { 'v1.1.0' }
+
+              it 'does not create a merge request pipeline' do
+                expect(pipeline).not_to be_persisted
+                expect(pipeline.errors[:tag]).to eq(["is not included in the list"])
+              end
+            end
+
+            context 'when merge request is created from a forked project' do
+              let(:merge_request) do
+                create(:merge_request,
+                  source_project: project,
+                  source_branch: ref_name,
+                  target_project: target_project,
+                  target_branch: 'master')
+              end
+
+              let!(:project) { fork_project(target_project, nil, repository: true) }
+              let!(:target_project) { create(:project, :repository) }
+
+              it 'creates a merge request pipeline in the forked project' do
+                expect(pipeline).to be_persisted
+                expect(project.pipelines).to eq([pipeline])
+                expect(target_project.pipelines).to be_empty
+              end
+            end
+
+            context "when there are no matched jobs" do
+              let(:config) do
+                {
+                  test: {
+                    stage: 'test',
+                    script: 'echo',
+                    except: ['merge_requests']
+                  }
+                }
+              end
+
+              it 'does not create a merge request pipeline' do
+                expect(pipeline).not_to be_persisted
+                expect(pipeline.errors[:base]).to eq(["No stages / jobs for this pipeline."])
+              end
+            end
+          end
+
+          context 'when merge request is not specified' do
+            let(:merge_request) { nil }
+
+            it 'does not create a merge request pipeline' do
+              expect(pipeline).not_to be_persisted
+              expect(pipeline.errors[:merge_request]).to eq(["can't be blank"])
+            end
+          end
+        end
+
+        context "when config does not have merge_requests keywords" do
+          let(:config) do
+            {
+              build: {
+                stage: 'build',
+                script: 'echo'
+              },
+              test: {
+                stage: 'test',
+                script: 'echo'
+              },
+              pages: {
+                stage: 'deploy',
+                script: 'echo'
+              }
+            }
+          end
+
+          context 'when merge request is specified' do
+            let(:merge_request) do
+              create(:merge_request,
+                source_project: project,
+                source_branch: ref_name,
+                target_project: project,
+                target_branch: 'master')
+            end
+
+            it 'does not create a merge request pipeline' do
+              expect(pipeline).not_to be_persisted
+
+              expect(pipeline.errors[:base])
+                .to eq(['No stages / jobs for this pipeline.'])
+            end
+          end
+
+          context 'when merge request is not specified' do
+            let(:merge_request) { nil }
+
+            it 'does not create a merge request pipeline' do
+              expect(pipeline).not_to be_persisted
+
+              expect(pipeline.errors[:base])
+                .to eq(['No stages / jobs for this pipeline.'])
+            end
+          end
+        end
+      end
+
+      context 'when source is web' do
+        let(:source) { :web }
+
+        context "when config has merge_requests keywords" do
+          let(:config) do
+            {
+              build: {
+                stage: 'build',
+                script: 'echo'
+              },
+              test: {
+                stage: 'test',
+                script: 'echo',
+                only: ['merge_requests']
+              },
+              pages: {
+                stage: 'deploy',
+                script: 'echo',
+                except: ['merge_requests']
+              }
+            }
+          end
+
+          context 'when merge request is specified' do
+            let(:merge_request) do
+              create(:merge_request,
+                source_project: project,
+                source_branch: ref_name,
+                target_project: project,
+                target_branch: 'master')
+            end
+
+            it 'does not create a merge request pipeline' do
+              expect(pipeline).not_to be_persisted
+              expect(pipeline.errors[:merge_request]).to eq(["must be blank"])
+            end
+          end
+
+          context 'when merge request is not specified' do
+            let(:merge_request) { nil }
+
+            it 'creates a branch pipeline' do
+              expect(pipeline).to be_persisted
+              expect(pipeline).to be_web
+              expect(pipeline.merge_request).to be_nil
+              expect(pipeline.builds.order(:stage_id).map(&:name)).to eq(%w[build pages])
+            end
+          end
+        end
+      end
+    end
   end
 
   describe '#execute!' do
diff --git a/spec/services/merge_requests/create_service_spec.rb b/spec/services/merge_requests/create_service_spec.rb
index 74bcc15f9123484cb8a599422f35dfa9f2b85c2a..5a3ecb1019be9111365cfdea56ba49cee1739205 100644
--- a/spec/services/merge_requests/create_service_spec.rb
+++ b/spec/services/merge_requests/create_service_spec.rb
@@ -159,6 +159,78 @@ describe MergeRequests::CreateService do
           end
         end
       end
+
+      describe 'Merge request pipelines' do
+        before do
+          stub_ci_pipeline_yaml_file(YAML.dump(config))
+        end
+
+        context "when .gitlab-ci.yml has merge_requests keywords" do
+          let(:config) do
+            {
+              test: {
+                stage: 'test',
+                script: 'echo',
+                only: ['merge_requests']
+              }
+            }
+          end
+
+          it 'creates a merge request pipeline and sets it as a head pipeline' do
+            expect(merge_request).to be_persisted
+
+            merge_request.reload
+            expect(merge_request.merge_request_pipelines.count).to eq(1)
+            expect(merge_request.actual_head_pipeline).to be_merge_request
+          end
+
+          context "when branch pipeline was created before a merge request pipline has been created" do
+            before do
+              create(:ci_pipeline, project: merge_request.source_project,
+                                   sha: merge_request.diff_head_sha,
+                                   ref: merge_request.source_branch,
+                                   tag: false)
+
+              merge_request
+            end
+
+            it 'sets the latest merge request pipeline as the head pipeline' do
+              expect(merge_request.actual_head_pipeline).to be_merge_request
+            end
+          end
+
+          context "when the 'ci_merge_request_pipeline' feature flag is disabled" do
+            before do
+              stub_feature_flags(ci_merge_request_pipeline: false)
+            end
+
+            it 'does not create a merge request pipeline' do
+              expect(merge_request).to be_persisted
+
+              merge_request.reload
+              expect(merge_request.merge_request_pipelines.count).to eq(0)
+            end
+          end
+        end
+
+        context "when .gitlab-ci.yml does not have merge_requests keywords" do
+          let(:config) do
+            {
+              test: {
+                stage: 'test',
+                script: 'echo'
+              }
+            }
+          end
+
+          it 'does not create a merge request pipeline' do
+            expect(merge_request).to be_persisted
+
+            merge_request.reload
+            expect(merge_request.merge_request_pipelines.count).to eq(0)
+          end
+        end
+      end
     end
 
     it_behaves_like 'new issuable record that supports quick actions' do
diff --git a/spec/services/merge_requests/refresh_service_spec.rb b/spec/services/merge_requests/refresh_service_spec.rb
index 2bee298a4d0962d71e0123d40db1583616ddd9b9..91c53edf5fed7250086192ea49b97ef7781fe293 100644
--- a/spec/services/merge_requests/refresh_service_spec.rb
+++ b/spec/services/merge_requests/refresh_service_spec.rb
@@ -138,6 +138,94 @@ describe MergeRequests::RefreshService do
       end
     end
 
+    describe 'Merge request pipelines' do
+      before do
+        stub_ci_pipeline_yaml_file(YAML.dump(config))
+      end
+
+      subject { service.new(@project, @user).execute(@oldrev, @newrev, 'refs/heads/master') }
+
+      context "when .gitlab-ci.yml has merge_requests keywords" do
+        let(:config) do
+          {
+            test: {
+              stage: 'test',
+              script: 'echo',
+              only: ['merge_requests']
+            }
+          }
+        end
+
+        it 'create merge request pipeline' do
+          expect { subject }
+            .to change { @merge_request.merge_request_pipelines.count }.by(1)
+            .and change { @fork_merge_request.merge_request_pipelines.count }.by(1)
+            .and change { @another_merge_request.merge_request_pipelines.count }.by(1)
+        end
+
+        context "when branch pipeline was created before a merge request pipline has been created" do
+          before do
+            create(:ci_pipeline, project: @merge_request.source_project,
+                                 sha: @merge_request.diff_head_sha,
+                                 ref: @merge_request.source_branch,
+                                 tag: false)
+
+            subject
+          end
+
+          it 'sets the latest merge request pipeline as a head pipeline' do
+            @merge_request.reload
+            expect(@merge_request.actual_head_pipeline).to be_merge_request
+          end
+
+          it 'returns pipelines in correct order' do
+            @merge_request.reload
+            expect(@merge_request.all_pipelines.first).to be_merge_request
+            expect(@merge_request.all_pipelines.second).to be_push
+          end
+        end
+
+        context "when MergeRequestUpdateWorker is retried by an exception" do
+          it 'does not re-create a duplicate merge request pipeline' do
+            expect do
+              service.new(@project, @user).execute(@oldrev, @newrev, 'refs/heads/master')
+            end.to change { @merge_request.merge_request_pipelines.count }.by(1)
+
+            expect do
+              service.new(@project, @user).execute(@oldrev, @newrev, 'refs/heads/master')
+            end.not_to change { @merge_request.merge_request_pipelines.count }
+          end
+        end
+
+        context "when the 'ci_merge_request_pipeline' feature flag is disabled" do
+          before do
+            stub_feature_flags(ci_merge_request_pipeline: false)
+          end
+
+          it 'does not create a merge request pipeline' do
+            expect { subject }
+              .not_to change { @merge_request.merge_request_pipelines.count }
+          end
+        end
+      end
+
+      context "when .gitlab-ci.yml does not have merge_requests keywords" do
+        let(:config) do
+          {
+            test: {
+              stage: 'test',
+              script: 'echo'
+            }
+          }
+        end
+
+        it 'does not create a merge request pipeline' do
+          expect { subject }
+            .not_to change { @merge_request.merge_request_pipelines.count }
+        end
+      end
+    end
+
     context 'push to origin repo source branch when an MR was reopened' do
       let(:refresh_service) { service.new(@project, @user) }
       let(:notification_service) { spy('notification_service') }
diff --git a/spec/workers/update_head_pipeline_for_merge_request_worker_spec.rb b/spec/workers/update_head_pipeline_for_merge_request_worker_spec.rb
index 9adde5fc21a258c80e96b9bb7cc78aee533decce..a2bc264b0f6c5c87e1629b6832004716b2d1de25 100644
--- a/spec/workers/update_head_pipeline_for_merge_request_worker_spec.rb
+++ b/spec/workers/update_head_pipeline_for_merge_request_worker_spec.rb
@@ -34,5 +34,33 @@ describe UpdateHeadPipelineForMergeRequestWorker do
         expect { subject.perform(merge_request.id) }.not_to change { merge_request.reload.head_pipeline_id }
       end
     end
+
+    context 'when a merge request pipeline exists' do
+      let!(:merge_request_pipeline) do
+        create(:ci_pipeline,
+               project: project,
+               source: :merge_request,
+               sha: latest_sha,
+               merge_request: merge_request)
+      end
+
+      it 'sets the merge request pipeline as the head pipeline' do
+        expect { subject.perform(merge_request.id) }
+          .to change { merge_request.reload.head_pipeline_id }
+          .from(nil).to(merge_request_pipeline.id)
+      end
+
+      context 'when branch pipeline exists' do
+        let!(:branch_pipeline) do
+          create(:ci_pipeline, project: project, source: :push, sha: latest_sha)
+        end
+
+        it 'prioritizes the merge request pipeline as the head pipeline' do
+          expect { subject.perform(merge_request.id) }
+            .to change { merge_request.reload.head_pipeline_id }
+            .from(nil).to(merge_request_pipeline.id)
+        end
+      end
+    end
   end
 end