Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
G
gitlab-ce
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
1
Merge Requests
1
Analytics
Analytics
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Commits
Issue Boards
Open sidebar
nexedi
gitlab-ce
Commits
e62bfc78
Commit
e62bfc78
authored
Dec 05, 2018
by
Shinya Maeda
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Merge request pipelines
parent
23d92198
Changes
26
Expand all
Hide whitespace changes
Inline
Side-by-side
Showing
26 changed files
with
1248 additions
and
28 deletions
+1248
-28
app/models/ci/pipeline.rb
app/models/ci/pipeline.rb
+20
-2
app/models/ci/pipeline_enums.rb
app/models/ci/pipeline_enums.rb
+2
-1
app/models/merge_request.rb
app/models/merge_request.rb
+9
-3
app/serializers/pipeline_entity.rb
app/serializers/pipeline_entity.rb
+1
-0
app/services/ci/create_pipeline_service.rb
app/services/ci/create_pipeline_service.rb
+2
-1
app/services/merge_requests/base_service.rb
app/services/merge_requests/base_service.rb
+18
-0
app/services/merge_requests/create_service.rb
app/services/merge_requests/create_service.rb
+2
-5
app/services/merge_requests/refresh_service.rb
app/services/merge_requests/refresh_service.rb
+1
-0
app/workers/update_head_pipeline_for_merge_request_worker.rb
app/workers/update_head_pipeline_for_merge_request_worker.rb
+3
-3
changelogs/unreleased/mr-pipelines-2.yml
changelogs/unreleased/mr-pipelines-2.yml
+5
-0
db/migrate/20181119081539_add_merge_request_id_to_ci_pipelines.rb
...te/20181119081539_add_merge_request_id_to_ci_pipelines.rb
+13
-0
db/migrate/20181120091639_add_foreign_key_to_ci_pipelines_merge_requests.rb
...0091639_add_foreign_key_to_ci_pipelines_merge_requests.rb
+22
-0
db/schema.rb
db/schema.rb
+3
-0
lib/gitlab/ci/pipeline/chain/build.rb
lib/gitlab/ci/pipeline/chain/build.rb
+1
-0
lib/gitlab/ci/pipeline/chain/command.rb
lib/gitlab/ci/pipeline/chain/command.rb
+1
-1
spec/features/merge_request/user_sees_merge_request_pipelines_spec.rb
...s/merge_request/user_sees_merge_request_pipelines_spec.rb
+333
-0
spec/lib/gitlab/ci/pipeline/chain/build_spec.rb
spec/lib/gitlab/ci/pipeline/chain/build_spec.rb
+29
-0
spec/lib/gitlab/ci/pipeline/chain/validate/config_spec.rb
spec/lib/gitlab/ci/pipeline/chain/validate/config_spec.rb
+30
-0
spec/lib/gitlab/import_export/all_models.yml
spec/lib/gitlab/import_export/all_models.yml
+2
-0
spec/lib/gitlab/import_export/safe_model_attributes.yml
spec/lib/gitlab/import_export/safe_model_attributes.yml
+1
-0
spec/models/ci/pipeline_spec.rb
spec/models/ci/pipeline_spec.rb
+234
-4
spec/models/merge_request_spec.rb
spec/models/merge_request_spec.rb
+113
-0
spec/services/ci/create_pipeline_service_spec.rb
spec/services/ci/create_pipeline_service_spec.rb
+215
-8
spec/services/merge_requests/create_service_spec.rb
spec/services/merge_requests/create_service_spec.rb
+72
-0
spec/services/merge_requests/refresh_service_spec.rb
spec/services/merge_requests/refresh_service_spec.rb
+88
-0
spec/workers/update_head_pipeline_for_merge_request_worker_spec.rb
...ers/update_head_pipeline_for_merge_request_worker_spec.rb
+28
-0
No files found.
app/models/ci/pipeline.rb
View file @
e62bfc78
...
...
@@ -16,6 +16,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
...
...
@@ -50,6 +51,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?
...
...
@@ -171,6 +175,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
...
...
@@ -372,7 +383,7 @@ module Ci
end
def
branch?
!
tag?
!
tag?
&&
!
merge_request?
end
def
stuck?
...
...
@@ -619,7 +630,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
)
...
...
@@ -696,6 +712,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
...
...
app/models/ci/pipeline_enums.rb
View file @
e62bfc78
...
...
@@ -21,7 +21,8 @@ module Ci
trigger:
3
,
schedule:
4
,
api:
5
,
external:
6
external:
6
,
merge_request:
9
}
end
end
...
...
app/models/merge_request.rb
View file @
e62bfc78
...
...
@@ -63,6 +63,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"
...
...
@@ -1052,12 +1053,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?
...
...
app/serializers/pipeline_entity.rb
View file @
e62bfc78
...
...
@@ -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
...
...
app/services/ci/create_pipeline_service.rb
View file @
e62bfc78
...
...
@@ -14,7 +14,7 @@ module Ci
Gitlab
::
Ci
::
Pipeline
::
Chain
::
Populate
,
Gitlab
::
Ci
::
Pipeline
::
Chain
::
Create
].
freeze
def
execute
(
source
,
ignore_skip_ci:
false
,
save_on_errors:
true
,
trigger_request:
nil
,
schedule:
nil
,
&
block
)
def
execute
(
source
,
ignore_skip_ci:
false
,
save_on_errors:
true
,
trigger_request:
nil
,
schedule:
nil
,
merge_request:
nil
,
&
block
)
@pipeline
=
Ci
::
Pipeline
.
new
command
=
Gitlab
::
Ci
::
Pipeline
::
Chain
::
Command
.
new
(
...
...
@@ -25,6 +25,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
,
...
...
app/services/merge_requests/base_service.rb
View file @
e62bfc78
...
...
@@ -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
])
...
...
app/services/merge_requests/create_service.rb
View file @
e62bfc78
...
...
@@ -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
...
...
app/services/merge_requests/refresh_service.rb
View file @
e62bfc78
...
...
@@ -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
...
...
app/workers/update_head_pipeline_for_merge_request_worker.rb
View file @
e62bfc78
...
...
@@ -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
(
...
...
changelogs/unreleased/mr-pipelines-2.yml
0 → 100644
View file @
e62bfc78
---
title
:
Merge request pipelines
merge_request
:
23217
author
:
type
:
added
db/migrate/20181119081539_add_merge_request_id_to_ci_pipelines.rb
0 → 100644
View file @
e62bfc78
# 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
db/migrate/20181120091639_add_foreign_key_to_ci_pipelines_merge_requests.rb
0 → 100644
View file @
e62bfc78
# 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
db/schema.rb
View file @
e62bfc78
...
...
@@ -474,7 +474,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
...
...
@@ -2292,6 +2294,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
...
...
lib/gitlab/ci/pipeline/chain/build.rb
View file @
e62bfc78
...
...
@@ -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
)
)
...
...
lib/gitlab/ci/pipeline/chain/command.rb
View file @
e62bfc78
...
...
@@ -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
)
do
...
...
spec/features/merge_request/user_sees_merge_request_pipelines_spec.rb
0 → 100644
View file @
e62bfc78
This diff is collapsed.
Click to expand it.
spec/lib/gitlab/ci/pipeline/chain/build_spec.rb
View file @
e62bfc78
...
...
@@ -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
spec/lib/gitlab/ci/pipeline/chain/validate/config_spec.rb
View file @
e62bfc78
...
...
@@ -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
spec/lib/gitlab/import_export/all_models.yml
View file @
e62bfc78
...
...
@@ -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
:
...
...
spec/lib/gitlab/import_export/safe_model_attributes.yml
View file @
e62bfc78
...
...
@@ -243,6 +243,7 @@ Ci::Pipeline:
-
failure_reason
-
protected
-
iid
-
merge_request_id
Ci::Stage:
-
id
-
name
...
...
spec/models/ci/pipeline_spec.rb
View file @
e62bfc78
...
...
@@ -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
)
}
...
...
@@ -37,6 +38,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
}
...
...
@@ -760,27 +883,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
...
...
@@ -1855,6 +2036,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
...
...
spec/models/merge_request_spec.rb
View file @
e62bfc78
...
...
@@ -1206,6 +1206,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
...
...
spec/services/ci/create_pipeline_service_spec.rb
View file @
e62bfc78
...
...
@@ -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
...
...
spec/services/merge_requests/create_service_spec.rb
View file @
e62bfc78
...
...
@@ -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
...
...
spec/services/merge_requests/refresh_service_spec.rb
View file @
e62bfc78
...
...
@@ -132,6 +132,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'
)
}
...
...
spec/workers/update_head_pipeline_for_merge_request_worker_spec.rb
View file @
e62bfc78
...
...
@@ -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
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment