Commit 072887b0 authored by Fabio Pitino's avatar Fabio Pitino Committed by Alessio Caiazza

Fix merge request child pipelines

Allow child pipeline to run in the context of
merge requests
parent ad938b48
...@@ -186,7 +186,8 @@ module Ci ...@@ -186,7 +186,8 @@ module Ci
}, },
execute_params: { execute_params: {
ignore_skip_ci: true, ignore_skip_ci: true,
bridge: self bridge: self,
merge_request: parent_pipeline.merge_request
} }
} }
end end
......
...@@ -77,9 +77,7 @@ module Ci ...@@ -77,9 +77,7 @@ module Ci
validates :sha, presence: { unless: :importing? } validates :sha, presence: { unless: :importing? }
validates :ref, presence: { unless: :importing? } validates :ref, presence: { unless: :importing? }
validates :merge_request, presence: { if: :merge_request_event? } validates :tag, inclusion: { in: [false], if: :merge_request? }
validates :merge_request, absence: { unless: :merge_request_event? }
validates :tag, inclusion: { in: [false], if: :merge_request_event? }
validates :external_pull_request, presence: { if: :external_pull_request_event? } validates :external_pull_request, presence: { if: :external_pull_request_event? }
validates :external_pull_request, absence: { unless: :external_pull_request_event? } validates :external_pull_request, absence: { unless: :external_pull_request_event? }
...@@ -662,7 +660,7 @@ module Ci ...@@ -662,7 +660,7 @@ module Ci
variables.concat(predefined_commit_variables) variables.concat(predefined_commit_variables)
if merge_request_event? && merge_request if merge_request?
variables.append(key: 'CI_MERGE_REQUEST_EVENT_TYPE', value: merge_request_event_type.to_s) variables.append(key: 'CI_MERGE_REQUEST_EVENT_TYPE', value: merge_request_event_type.to_s)
variables.append(key: 'CI_MERGE_REQUEST_SOURCE_BRANCH_SHA', value: source_sha.to_s) variables.append(key: 'CI_MERGE_REQUEST_SOURCE_BRANCH_SHA', value: source_sha.to_s)
variables.append(key: 'CI_MERGE_REQUEST_TARGET_BRANCH_SHA', value: target_sha.to_s) variables.append(key: 'CI_MERGE_REQUEST_TARGET_BRANCH_SHA', value: target_sha.to_s)
...@@ -720,7 +718,7 @@ module Ci ...@@ -720,7 +718,7 @@ module Ci
# All the merge requests for which the current pipeline runs/ran against # All the merge requests for which the current pipeline runs/ran against
def all_merge_requests def all_merge_requests
@all_merge_requests ||= @all_merge_requests ||=
if merge_request_event? if merge_request?
MergeRequest.where(id: merge_request_id) MergeRequest.where(id: merge_request_id)
else else
MergeRequest.where(source_project_id: project_id, source_branch: ref) MergeRequest.where(source_project_id: project_id, source_branch: ref)
...@@ -812,7 +810,7 @@ module Ci ...@@ -812,7 +810,7 @@ module Ci
# * nil: Modified path can not be evaluated # * nil: Modified path can not be evaluated
def modified_paths def modified_paths
strong_memoize(:modified_paths) do strong_memoize(:modified_paths) do
if merge_request_event? if merge_request?
merge_request.modified_paths merge_request.modified_paths
elsif branch_updated? elsif branch_updated?
push_details.modified_paths push_details.modified_paths
...@@ -836,12 +834,12 @@ module Ci ...@@ -836,12 +834,12 @@ module Ci
ref == project.default_branch ref == project.default_branch
end end
def triggered_by_merge_request? def merge_request?
merge_request_event? && merge_request_id.present? merge_request_id.present?
end end
def detached_merge_request_pipeline? def detached_merge_request_pipeline?
triggered_by_merge_request? && target_sha.nil? merge_request? && target_sha.nil?
end end
def legacy_detached_merge_request_pipeline? def legacy_detached_merge_request_pipeline?
...@@ -849,7 +847,7 @@ module Ci ...@@ -849,7 +847,7 @@ module Ci
end end
def merge_request_pipeline? def merge_request_pipeline?
triggered_by_merge_request? && target_sha.present? merge_request? && target_sha.present?
end end
def merge_request_ref? def merge_request_ref?
...@@ -865,7 +863,7 @@ module Ci ...@@ -865,7 +863,7 @@ module Ci
end end
def source_ref def source_ref
if triggered_by_merge_request? if merge_request?
merge_request.source_branch merge_request.source_branch
else else
ref ref
...@@ -885,7 +883,7 @@ module Ci ...@@ -885,7 +883,7 @@ module Ci
end end
def merge_request_event_type def merge_request_event_type
return unless merge_request_event? return unless merge_request?
strong_memoize(:merge_request_event_type) do strong_memoize(:merge_request_event_type) do
if merge_request_pipeline? if merge_request_pipeline?
...@@ -918,7 +916,7 @@ module Ci ...@@ -918,7 +916,7 @@ module Ci
def git_ref def git_ref
strong_memoize(:git_ref) do strong_memoize(:git_ref) do
if merge_request_event? if merge_request?
## ##
# In the future, we're going to change this ref to # In the future, we're going to change this ref to
# merge request's merged reference, such as "refs/merge-requests/:iid/merge". # merge request's merged reference, such as "refs/merge-requests/:iid/merge".
......
...@@ -11,7 +11,7 @@ module Ci ...@@ -11,7 +11,7 @@ module Ci
extend ActiveSupport::Concern extend ActiveSupport::Concern
included do included do
delegate :merge_request_event?, delegate :merge_request?,
:merge_request_ref?, :merge_request_ref?,
:legacy_detached_merge_request_pipeline?, :legacy_detached_merge_request_pipeline?,
:merge_train_pipeline?, to: :pipeline :merge_train_pipeline?, to: :pipeline
......
...@@ -7,7 +7,7 @@ module HasRef ...@@ -7,7 +7,7 @@ module HasRef
extend ActiveSupport::Concern extend ActiveSupport::Concern
def branch? def branch?
!tag? && !merge_request_event? !tag? && !merge_request?
end end
def git_ref def git_ref
......
...@@ -1163,7 +1163,7 @@ class MergeRequest < ApplicationRecord ...@@ -1163,7 +1163,7 @@ class MergeRequest < ApplicationRecord
# Since deployments run on a merge request ref (e.g. `refs/merge-requests/:iid/head`), # Since deployments run on a merge request ref (e.g. `refs/merge-requests/:iid/head`),
# we cannot look up environments with source branch name. # we cannot look up environments with source branch name.
def environments def environments
return Environment.none unless actual_head_pipeline&.triggered_by_merge_request? return Environment.none unless actual_head_pipeline&.merge_request?
actual_head_pipeline.environments actual_head_pipeline.environments
end end
......
...@@ -61,6 +61,8 @@ class MergeRequest::Pipelines ...@@ -61,6 +61,8 @@ class MergeRequest::Pipelines
pipelines.joins(shas_table) pipelines.joins(shas_table)
end end
# NOTE: this method returns only parent merge request pipelines.
# Child merge request pipelines have a different source.
def triggered_by_merge_request def triggered_by_merge_request
source_project.ci_pipelines source_project.ci_pipelines
.where(source: :merge_request_event, merge_request: merge_request) .where(source: :merge_request_event, merge_request: merge_request)
......
...@@ -116,7 +116,7 @@ module Ci ...@@ -116,7 +116,7 @@ module Ci
def merge_request_presenter def merge_request_presenter
strong_memoize(:merge_request_presenter) do strong_memoize(:merge_request_presenter) do
if pipeline.triggered_by_merge_request? if pipeline.merge_request?
pipeline.merge_request.present(current_user: current_user) pipeline.merge_request.present(current_user: current_user)
end end
end end
......
...@@ -25,7 +25,7 @@ class PipelineEntity < Grape::Entity ...@@ -25,7 +25,7 @@ class PipelineEntity < Grape::Entity
expose :flags do expose :flags do
expose :stuck?, as: :stuck expose :stuck?, as: :stuck
expose :auto_devops_source?, as: :auto_devops expose :auto_devops_source?, as: :auto_devops
expose :merge_request_event?, as: :merge_request expose :merge_request?, as: :merge_request
expose :has_yaml_errors?, as: :yaml_errors expose :has_yaml_errors?, as: :yaml_errors
expose :can_retry?, as: :retryable expose :can_retry?, as: :retryable
expose :can_cancel?, as: :cancelable expose :can_cancel?, as: :cancelable
...@@ -59,11 +59,11 @@ class PipelineEntity < Grape::Entity ...@@ -59,11 +59,11 @@ class PipelineEntity < Grape::Entity
expose :tag?, as: :tag expose :tag?, as: :tag
expose :branch?, as: :branch expose :branch?, as: :branch
expose :merge_request_event?, as: :merge_request expose :merge_request?, as: :merge_request
end end
expose :commit, using: CommitEntity expose :commit, using: CommitEntity
expose :merge_request_event_type, if: -> (pipeline, _) { pipeline.merge_request_event? } expose :merge_request_event_type, if: -> (pipeline, _) { pipeline.merge_request? }
expose :source_sha, if: -> (pipeline, _) { pipeline.merge_request_pipeline? } expose :source_sha, if: -> (pipeline, _) { pipeline.merge_request_pipeline? }
expose :target_sha, if: -> (pipeline, _) { pipeline.merge_request_pipeline? } expose :target_sha, if: -> (pipeline, _) { pipeline.merge_request_pipeline? }
expose :yaml_errors, if: -> (pipeline, _) { pipeline.has_yaml_errors? } expose :yaml_errors, if: -> (pipeline, _) { pipeline.has_yaml_errors? }
...@@ -104,7 +104,7 @@ class PipelineEntity < Grape::Entity ...@@ -104,7 +104,7 @@ class PipelineEntity < Grape::Entity
end end
def has_presentable_merge_request? def has_presentable_merge_request?
pipeline.triggered_by_merge_request? && pipeline.merge_request? &&
can?(request.current_user, :read_merge_request, pipeline.merge_request) can?(request.current_user, :read_merge_request, pipeline.merge_request)
end end
......
...@@ -24,7 +24,7 @@ module MergeRequests ...@@ -24,7 +24,7 @@ module MergeRequests
## ##
# UpdateMergeRequestsWorker could be retried by an exception. # UpdateMergeRequestsWorker could be retried by an exception.
# pipelines for merge request should not be recreated in such case. # pipelines for merge request should not be recreated in such case.
return false if !allow_duplicate && merge_request.find_actual_head_pipeline&.triggered_by_merge_request? return false if !allow_duplicate && merge_request.find_actual_head_pipeline&.merge_request?
return false if merge_request.has_no_commits? return false if merge_request.has_no_commits?
true true
......
---
title: Allow running child pipelines as merge request pipelines
merge_request: 23884
author:
type: fixed
...@@ -132,7 +132,7 @@ module EE ...@@ -132,7 +132,7 @@ module EE
override :merge_request_event_type override :merge_request_event_type
def merge_request_event_type def merge_request_event_type
return unless merge_request_event? return unless merge_request?
strong_memoize(:merge_request_event_type) do strong_memoize(:merge_request_event_type) do
merge_train_pipeline? ? :merge_train : super merge_train_pipeline? ? :merge_train : super
......
...@@ -36,7 +36,7 @@ describe MergeRequests::CreateService do ...@@ -36,7 +36,7 @@ describe MergeRequests::CreateService do
context 'report approvers' do context 'report approvers' do
let(:sha) { project.repository.commits(opts[:source_branch], limit: 1).first.id } let(:sha) { project.repository.commits(opts[:source_branch], limit: 1).first.id }
let(:pipeline) { instance_double(Ci::Pipeline, id: 42, project_id: project.id, triggered_by_merge_request?: true) } let(:pipeline) { instance_double(Ci::Pipeline, id: 42, project_id: project.id, merge_request?: true) }
it 'refreshes report approvers for the merge request' do it 'refreshes report approvers for the merge request' do
expect_next_instance_of(::MergeRequests::SyncReportApproverApprovalRules) do |service| expect_next_instance_of(::MergeRequests::SyncReportApproverApprovalRules) do |service|
......
...@@ -33,7 +33,7 @@ describe Ci::Build do ...@@ -33,7 +33,7 @@ describe Ci::Build do
it { is_expected.to respond_to(:has_trace?) } it { is_expected.to respond_to(:has_trace?) }
it { is_expected.to respond_to(:trace) } it { is_expected.to respond_to(:trace) }
it { is_expected.to delegate_method(:merge_request_event?).to(:pipeline) } it { is_expected.to delegate_method(:merge_request?).to(:pipeline) }
it { is_expected.to delegate_method(:merge_request_ref?).to(:pipeline) } it { is_expected.to delegate_method(:merge_request_ref?).to(:pipeline) }
it { is_expected.to delegate_method(:legacy_detached_merge_request_pipeline?).to(:pipeline) } it { is_expected.to delegate_method(:legacy_detached_merge_request_pipeline?).to(:pipeline) }
......
...@@ -162,6 +162,23 @@ describe Ci::Pipeline, :mailer do ...@@ -162,6 +162,23 @@ describe Ci::Pipeline, :mailer do
end end
end end
describe '#merge_request?' do
let(:pipeline) { create(:ci_pipeline, merge_request: merge_request) }
let(:merge_request) { create(:merge_request) }
it 'returns true' do
expect(pipeline).to be_merge_request
end
context 'when merge request is nil' do
let(:merge_request) { nil }
it 'returns false' do
expect(pipeline).not_to be_merge_request
end
end
end
describe '#detached_merge_request_pipeline?' do describe '#detached_merge_request_pipeline?' do
subject { pipeline.detached_merge_request_pipeline? } subject { pipeline.detached_merge_request_pipeline? }
...@@ -367,48 +384,6 @@ describe Ci::Pipeline, :mailer do ...@@ -367,48 +384,6 @@ describe Ci::Pipeline, :mailer do
end end
end end
describe 'Validations for merge request pipelines' do
let(:pipeline) do
build(:ci_pipeline, source: source, 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
context 'when source is merge request' do
let(:source) { :merge_request_event }
context 'when merge request is specified' do
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
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 describe 'modules' do
it_behaves_like 'AtomicInternalId', validate_presence: false do it_behaves_like 'AtomicInternalId', validate_presence: false do
let(:internal_id_attribute) { :iid } let(:internal_id_attribute) { :iid }
...@@ -612,9 +587,9 @@ describe Ci::Pipeline, :mailer do ...@@ -612,9 +587,9 @@ describe Ci::Pipeline, :mailer do
] ]
end end
context 'when source is merge request' do context 'when pipeline is merge request' do
let(:pipeline) do let(:pipeline) do
create(:ci_pipeline, source: :merge_request_event, merge_request: merge_request) create(:ci_pipeline, merge_request: merge_request)
end end
let(:merge_request) do let(:merge_request) do
...@@ -651,7 +626,7 @@ describe Ci::Pipeline, :mailer do ...@@ -651,7 +626,7 @@ describe Ci::Pipeline, :mailer do
'CI_MERGE_REQUEST_TITLE' => merge_request.title, 'CI_MERGE_REQUEST_TITLE' => merge_request.title,
'CI_MERGE_REQUEST_ASSIGNEES' => merge_request.assignee_username_list, 'CI_MERGE_REQUEST_ASSIGNEES' => merge_request.assignee_username_list,
'CI_MERGE_REQUEST_MILESTONE' => milestone.title, 'CI_MERGE_REQUEST_MILESTONE' => milestone.title,
'CI_MERGE_REQUEST_LABELS' => labels.map(&:title).join(','), 'CI_MERGE_REQUEST_LABELS' => labels.map(&:title).sort.join(','),
'CI_MERGE_REQUEST_EVENT_TYPE' => pipeline.merge_request_event_type.to_s) 'CI_MERGE_REQUEST_EVENT_TYPE' => pipeline.merge_request_event_type.to_s)
end end
...@@ -1263,9 +1238,9 @@ describe Ci::Pipeline, :mailer do ...@@ -1263,9 +1238,9 @@ describe Ci::Pipeline, :mailer do
is_expected.to be_truthy is_expected.to be_truthy
end end
context 'when source is merge request' do context 'when pipeline is merge request' do
let(:pipeline) do let(:pipeline) do
create(:ci_pipeline, source: :merge_request_event, merge_request: merge_request) create(:ci_pipeline, merge_request: merge_request)
end end
let(:merge_request) do let(:merge_request) do
......
...@@ -148,6 +148,12 @@ describe Ci::CreateCrossProjectPipelineService, '#execute' do ...@@ -148,6 +148,12 @@ describe Ci::CreateCrossProjectPipelineService, '#execute' do
end end
context 'when "include" is provided' do context 'when "include" is provided' do
let(:file_content) do
YAML.dump(
rspec: { script: 'rspec' },
echo: { script: 'echo' })
end
shared_examples 'creates a child pipeline' do shared_examples 'creates a child pipeline' do
it 'creates only one new pipeline' do it 'creates only one new pipeline' do
expect { service.execute(bridge) } expect { service.execute(bridge) }
...@@ -189,9 +195,6 @@ describe Ci::CreateCrossProjectPipelineService, '#execute' do ...@@ -189,9 +195,6 @@ describe Ci::CreateCrossProjectPipelineService, '#execute' do
end end
before do before do
file_content = YAML.dump(
rspec: { script: 'rspec' },
echo: { script: 'echo' })
upstream_project.repository.create_file( upstream_project.repository.create_file(
user, 'child-pipeline.yml', file_content, message: 'message', branch_name: 'master') user, 'child-pipeline.yml', file_content, message: 'message', branch_name: 'master')
...@@ -218,6 +221,29 @@ describe Ci::CreateCrossProjectPipelineService, '#execute' do ...@@ -218,6 +221,29 @@ describe Ci::CreateCrossProjectPipelineService, '#execute' do
it_behaves_like 'creates a child pipeline' it_behaves_like 'creates a child pipeline'
end end
context 'when the parent is a merge request pipeline' do
let(:merge_request) { create(:merge_request, source_project: bridge.project, target_project: bridge.project) }
let(:file_content) do
YAML.dump(
workflow: { rules: [{ if: '$CI_MERGE_REQUEST_ID' }] },
rspec: { script: 'rspec' },
echo: { script: 'echo' })
end
before do
bridge.pipeline.update!(source: :merge_request_event, merge_request: merge_request)
end
it_behaves_like 'creates a child pipeline'
it 'propagates the merge request to the child pipeline' do
pipeline = service.execute(bridge)
expect(pipeline.merge_request).to eq(merge_request)
expect(pipeline).to be_merge_request
end
end
context 'when upstream pipeline is a child pipeline' do context 'when upstream pipeline is a child pipeline' do
let!(:pipeline_source) do let!(:pipeline_source) do
create(:ci_sources_pipeline, create(:ci_sources_pipeline,
......
...@@ -1473,15 +1473,6 @@ describe Ci::CreatePipelineService do ...@@ -1473,15 +1473,6 @@ describe Ci::CreatePipelineService do
end end
end end
end end
context 'when merge request is not specified' do
let(:merge_request) { nil }
it 'does not create a detached merge request pipeline' do
expect(pipeline).not_to be_persisted
expect(pipeline.errors[:merge_request]).to eq(["can't be blank"])
end
end
end end
context "when config does not have merge_requests keywords" do context "when config does not have merge_requests keywords" do
...@@ -1518,17 +1509,6 @@ describe Ci::CreatePipelineService do ...@@ -1518,17 +1509,6 @@ describe Ci::CreatePipelineService do
.to eq(['No stages / jobs for this pipeline.']) .to eq(['No stages / jobs for this pipeline.'])
end end
end end
context 'when merge request is not specified' do
let(:merge_request) { nil }
it 'does not create a detached 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 config uses regular expression for only keyword" do context "when config uses regular expression for only keyword" do
...@@ -1623,6 +1603,7 @@ describe Ci::CreatePipelineService do ...@@ -1623,6 +1603,7 @@ describe Ci::CreatePipelineService do
context 'when source is web' do context 'when source is web' do
let(:source) { :web } let(:source) { :web }
let(:merge_request) { nil }
context "when config has merge_requests keywords" do context "when config has merge_requests keywords" do
let(:config) do let(:config) do
...@@ -1644,24 +1625,6 @@ describe Ci::CreatePipelineService do ...@@ -1644,24 +1625,6 @@ describe Ci::CreatePipelineService do
} }
end end
context 'when merge request is specified' do
let(:merge_request) do
create(:merge_request,
source_project: project,
source_branch: Gitlab::Git.ref_name(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 it 'creates a branch pipeline' do
expect(pipeline).to be_persisted expect(pipeline).to be_persisted
expect(pipeline).to be_web expect(pipeline).to be_web
...@@ -1671,7 +1634,6 @@ describe Ci::CreatePipelineService do ...@@ -1671,7 +1634,6 @@ describe Ci::CreatePipelineService do
end end
end end
end end
end
context 'when needs is used' do context 'when needs is used' do
let(:pipeline) { execute_service } let(:pipeline) { execute_service }
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment