Commit f2c9081c authored by Grzegorz Bizon's avatar Grzegorz Bizon

Merge branch 'patch-211' into 'master'

Add CI_OPEN_MERGE_REQUESTS environment variable

See merge request gitlab-org/gitlab!38673
parents 86a35180 6122ba8d
......@@ -7,6 +7,7 @@ module Ci
include Importable
include AfterCommitQueue
include Presentable
include Gitlab::Allowable
include Gitlab::OptimisticLocking
include Gitlab::Utils::StrongMemoize
include AtomicInternalId
......@@ -16,6 +17,8 @@ module Ci
include FromUnion
include UpdatedAtFilterable
MAX_OPEN_MERGE_REQUESTS_REFS = 4
PROJECT_ROUTE_AND_NAMESPACE_ROUTE = {
project: [:project_feature, :route, { namespace: :route }]
}.freeze
......@@ -801,6 +804,10 @@ module Ci
variables.concat(merge_request.predefined_variables)
end
if Gitlab::Ci::Features.pipeline_open_merge_requests?(project) && open_merge_requests_refs.any?
variables.append(key: 'CI_OPEN_MERGE_REQUESTS', value: open_merge_requests_refs.join(','))
end
variables.append(key: 'CI_KUBERNETES_ACTIVE', value: 'true') if has_kubernetes_active?
variables.append(key: 'CI_DEPLOY_FREEZE', value: 'true') if freeze_period?
......@@ -867,6 +874,36 @@ module Ci
all_merge_requests.order(id: :desc)
end
# This returns a list of MRs that point
# to the same source project/branch
def related_merge_requests
if merge_request?
# We look for all other MRs that this branch might be pointing to
MergeRequest.where(
source_project_id: merge_request.source_project_id,
source_branch: merge_request.source_branch)
else
MergeRequest.where(
source_project_id: project_id,
source_branch: ref)
end
end
# We cannot use `all_merge_requests`, due to race condition
# This returns a list of at most 4 open MRs
def open_merge_requests_refs
strong_memoize(:open_merge_requests_refs) do
# We ensure that triggering user can actually read the pipeline
related_merge_requests
.opened
.limit(MAX_OPEN_MERGE_REQUESTS_REFS)
.order(id: :desc)
.preload(:target_project)
.select { |mr| can?(user, :read_merge_request, mr) }
.map { |mr| mr.to_reference(project, full: true) }
end
end
def same_family_pipeline_ids
::Gitlab::Ci::PipelineObjectHierarchy.new(
self.class.where(id: root_ancestor), options: { same_project: true }
......
---
title: Add CI_OPEN_MERGE_REQUESTS environment variable
merge_request: 38673
author: Ben Bodenmiller @bbodenmiller
type: added
---
name: ci_pipeline_open_merge_requests
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/38673
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/292727
group: group::memory
type: development
default_enabled: false
......@@ -68,6 +68,7 @@ Kubernetes-specific environment variables are detailed in the
| `CI_EXTERNAL_PULL_REQUEST_TARGET_BRANCH_NAME` | 12.3 | all | The target branch name of the pull request if [the pipelines are for external pull requests](../ci_cd_for_external_repos/index.md#pipelines-for-external-pull-requests). Available only if `only: [external_pull_requests]` or [`rules`](../yaml/README.md#rules) syntax is used and the pull request is open. |
| `CI_EXTERNAL_PULL_REQUEST_TARGET_BRANCH_SHA` | 12.3 | all | The HEAD SHA of the target branch of the pull request if [the pipelines are for external pull requests](../ci_cd_for_external_repos/index.md#pipelines-for-external-pull-requests). Available only if `only: [external_pull_requests]` or [`rules`](../yaml/README.md#rules) syntax is used and the pull request is open. |
| `CI_HAS_OPEN_REQUIREMENTS` | 13.1 | all | Included with the value `true` only if the pipeline's project has any open [requirements](../../user/project/requirements/index.md). Not included if there are no open requirements for the pipeline's project. |
| `CI_OPEN_MERGE_REQUESTS` | 13.7 | all | Contains a comma-delimited list of up to 4 Merge Requests from the current source project and branch in the form `gitlab-org/gitlab!333,gitlab-org/gitlab-foss!11` |
| `CI_JOB_ID` | 9.0 | all | The unique ID of the current job that GitLab CI/CD uses internally |
| `CI_JOB_IMAGE` | 12.9 | 12.9 | The name of the image running the CI job |
| `CI_JOB_MANUAL` | 8.12 | all | The flag to indicate that job was manually started |
......
......@@ -55,6 +55,10 @@ module Gitlab
::Feature.enabled?(:ci_trace_log_invalid_chunks, project, type: :ops, default_enabled: false)
end
def self.pipeline_open_merge_requests?(project)
::Feature.enabled?(:ci_pipeline_open_merge_requests, project, default_enabled: false)
end
def self.seed_block_run_before_workflow_rules_enabled?(project)
::Feature.enabled?(:ci_seed_block_run_before_workflow_rules, project, default_enabled: true)
end
......
......@@ -795,11 +795,7 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep do
]
end
context 'when pipeline is merge request' do
let(:pipeline) do
create(:ci_pipeline, :detached_merge_request_pipeline, merge_request: merge_request)
end
context 'when merge request is present' do
let(:merge_request) do
create(:merge_request,
source_project: project,
......@@ -815,6 +811,18 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep do
let(:milestone) { create(:milestone, project: project) }
let(:labels) { create_list(:label, 2) }
context 'when pipeline for merge request is created' do
let(:pipeline) do
create(:ci_pipeline, :detached_merge_request_pipeline,
ci_ref_presence: false,
user: user,
merge_request: merge_request)
end
before do
project.add_developer(user)
end
it 'exposes merge request pipeline variables' do
expect(subject.to_hash)
.to include(
......@@ -835,7 +843,8 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep do
'CI_MERGE_REQUEST_ASSIGNEES' => merge_request.assignee_username_list,
'CI_MERGE_REQUEST_MILESTONE' => milestone.title,
'CI_MERGE_REQUEST_LABELS' => labels.map(&:title).sort.join(','),
'CI_MERGE_REQUEST_EVENT_TYPE' => 'detached')
'CI_MERGE_REQUEST_EVENT_TYPE' => 'detached',
'CI_OPEN_MERGE_REQUESTS' => merge_request.to_reference(full: true))
end
it 'exposes diff variables' do
......@@ -868,6 +877,42 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep do
expect(subject.to_hash.keys).not_to include('CI_MERGE_REQUEST_LABELS')
end
end
end
context 'when pipeline on branch is created' do
let(:pipeline) do
create(:ci_pipeline, project: project, user: user, ref: 'feature')
end
context 'when a merge request is created' do
before do
merge_request
end
context 'when user has access to project' do
before do
project.add_developer(user)
end
it 'merge request references are returned matching the pipeline' do
expect(subject.to_hash).to include(
'CI_OPEN_MERGE_REQUESTS' => merge_request.to_reference(full: true))
end
end
context 'when user does not have access to project' do
it 'CI_OPEN_MERGE_REQUESTS is not returned' do
expect(subject.to_hash).not_to have_key('CI_OPEN_MERGE_REQUESTS')
end
end
end
context 'when no a merge request is created' do
it 'CI_OPEN_MERGE_REQUESTS is not returned' do
expect(subject.to_hash).not_to have_key('CI_OPEN_MERGE_REQUESTS')
end
end
end
context 'with merged results' do
let(:pipeline) do
......@@ -2862,6 +2907,93 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep do
end
end
describe '#related_merge_requests' do
let(:project) { create(:project, :repository) }
let(:merge_request) { create(:merge_request, source_project: project, source_branch: 'feature', target_branch: 'master') }
let(:other_merge_request) { create(:merge_request, source_project: project, source_branch: 'feature', target_branch: 'stable') }
let(:branch_pipeline) { create(:ci_pipeline, project: project, ref: 'feature') }
let(:merge_pipeline) { create(:ci_pipeline, :detached_merge_request_pipeline, merge_request: merge_request) }
context 'for a branch pipeline' do
subject { branch_pipeline.related_merge_requests }
it 'when no merge request is created' do
is_expected.to be_empty
end
it 'when another merge requests are created' do
merge_request
other_merge_request
is_expected.to contain_exactly(merge_request, other_merge_request)
end
end
context 'for a merge pipeline' do
subject { merge_pipeline.related_merge_requests }
it 'when only merge pipeline is created' do
merge_pipeline
is_expected.to contain_exactly(merge_request)
end
it 'when a merge request is created' do
merge_pipeline
other_merge_request
is_expected.to contain_exactly(merge_request, other_merge_request)
end
end
end
describe '#open_merge_requests_refs' do
let(:project) { create(:project) }
let(:user) { create(:user) }
let!(:pipeline) { create(:ci_pipeline, user: user, project: project, ref: 'feature') }
let!(:merge_request) { create(:merge_request, source_project: project, source_branch: 'feature', target_branch: 'master') }
subject { pipeline.open_merge_requests_refs }
context 'when user is a developer' do
before do
project.add_developer(user)
end
it 'returns open merge requests' do
is_expected.to eq([merge_request.to_reference(full: true)])
end
it 'does not return closed merge requests' do
merge_request.close!
is_expected.to be_empty
end
context 'limits amount of returned merge requests' do
let!(:other_merge_requests) do
Array.new(4) do |idx|
create(:merge_request, source_project: project, source_branch: 'feature', target_branch: "master-#{idx}")
end
end
let(:other_merge_requests_refs) do
other_merge_requests.map { |mr| mr.to_reference(full: true) }
end
it 'returns only last 4 in a reverse order' do
is_expected.to eq(other_merge_requests_refs.reverse)
end
end
end
context 'when user does not have permissions' do
it 'does not return any merge requests' do
is_expected.to be_empty
end
end
end
describe '#same_family_pipeline_ids' do
subject { pipeline.same_family_pipeline_ids.map(&:id) }
......
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Ci::CreatePipelineService do
context 'merge requests handling' do
let_it_be(:project) { create(:project, :repository) }
let_it_be(:user) { project.owner }
let(:ref) { 'refs/heads/feature' }
let(:source) { :push }
let(:service) { described_class.new(project, user, { ref: ref }) }
let(:pipeline) { service.execute(source) }
before do
stub_ci_pipeline_yaml_file <<-EOS
workflow:
rules:
# do not create pipelines if merge requests are opened
- if: $CI_OPEN_MERGE_REQUESTS
when: never
- if: $CI_COMMIT_BRANCH
rspec:
script: echo Hello World
EOS
end
context 'when pushing a change' do
context 'when a merge request already exists' do
let!(:merge_request) do
create(:merge_request,
source_project: project,
source_branch: 'feature',
target_project: project,
target_branch: 'master')
end
it 'does not create a pipeline' do
expect(pipeline).not_to be_persisted
end
end
context 'when no merge request exists' do
it 'does create a pipeline' do
expect(pipeline.errors).to be_empty
expect(pipeline).to be_persisted
end
end
end
end
end
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