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
0d880dbb
Commit
0d880dbb
authored
May 24, 2019
by
Shinya Maeda
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Add auto merge strategies
AddToMergeTrainWhenPipelineSucceeds and MergeTrain
parent
bb88a1ae
Changes
20
Show whitespace changes
Inline
Side-by-side
Showing
20 changed files
with
1122 additions
and
49 deletions
+1122
-49
app/services/auto_merge_service.rb
app/services/auto_merge_service.rb
+2
-0
ee/app/models/ee/merge_request.rb
ee/app/models/ee/merge_request.rb
+0
-8
ee/app/models/merge_train.rb
ee/app/models/merge_train.rb
+4
-0
ee/app/services/auto_merge/merge_train_service.rb
ee/app/services/auto_merge/merge_train_service.rb
+43
-0
ee/app/services/ee/auto_merge_service.rb
ee/app/services/ee/auto_merge_service.rb
+22
-0
ee/app/services/ee/system_note_service.rb
ee/app/services/ee/system_note_service.rb
+21
-0
ee/app/services/merge_trains/create_pipeline_service.rb
ee/app/services/merge_trains/create_pipeline_service.rb
+41
-0
ee/app/services/merge_trains/refresh_merge_request_service.rb
...pp/services/merge_trains/refresh_merge_request_service.rb
+107
-0
ee/app/services/merge_trains/refresh_merge_requests_service.rb
...p/services/merge_trains/refresh_merge_requests_service.rb
+31
-0
ee/changelogs/unreleased/add-merge-train-auto-merge-strategy.yml
...gelogs/unreleased/add-merge-train-auto-merge-strategy.yml
+5
-0
ee/spec/factories/merge_requests.rb
ee/spec/factories/merge_requests.rb
+7
-1
ee/spec/features/merge_trains/two_merge_requests_on_train_spec.rb
...features/merge_trains/two_merge_requests_on_train_spec.rb
+188
-0
ee/spec/models/merge_request_spec.rb
ee/spec/models/merge_request_spec.rb
+0
-40
ee/spec/models/merge_train_spec.rb
ee/spec/models/merge_train_spec.rb
+25
-0
ee/spec/services/auto_merge/merge_train_service_spec.rb
ee/spec/services/auto_merge/merge_train_service_spec.rb
+218
-0
ee/spec/services/ee/auto_merge_service_spec.rb
ee/spec/services/ee/auto_merge_service_spec.rb
+13
-0
ee/spec/services/merge_trains/create_pipeline_service_spec.rb
...pec/services/merge_trains/create_pipeline_service_spec.rb
+110
-0
ee/spec/services/merge_trains/refresh_merge_request_service_spec.rb
...rvices/merge_trains/refresh_merge_request_service_spec.rb
+148
-0
ee/spec/services/merge_trains/refresh_merge_requests_service_spec.rb
...vices/merge_trains/refresh_merge_requests_service_spec.rb
+90
-0
ee/spec/services/system_note_service_spec.rb
ee/spec/services/system_note_service_spec.rb
+47
-0
No files found.
app/services/auto_merge_service.rb
View file @
0d880dbb
...
...
@@ -54,3 +54,5 @@ class AutoMergeService < BaseService
self
.
class
.
get_service_class
(
strategy
)
&
.
new
(
project
,
current_user
,
params
)
end
end
AutoMergeService
.
prepend
(
EE
::
AutoMergeService
)
ee/app/models/ee/merge_request.rb
View file @
0d880dbb
...
...
@@ -71,14 +71,6 @@ module EE
end
end
def
get_on_train!
(
user
)
create_merge_train!
(
user:
user
,
target_project:
target_project
,
target_branch:
target_branch
)
end
def
get_off_train!
merge_train
.
destroy!
end
def
on_train?
merge_train
.
present?
end
...
...
ee/app/models/merge_train.rb
View file @
0d880dbb
...
...
@@ -30,6 +30,10 @@ class MergeTrain < ApplicationRecord
self
.
class
.
all_in_train
(
merge_request
).
where
(
'merge_trains.id > ?'
,
id
)
end
def
next
all_next
.
first
end
def
index
self
.
class
.
all_in_train
(
merge_request
).
where
(
'merge_trains.id < ?'
,
id
).
count
end
...
...
ee/app/services/auto_merge/merge_train_service.rb
0 → 100644
View file @
0d880dbb
# frozen_string_literal: true
module
AutoMerge
class
MergeTrainService
<
AutoMerge
::
BaseService
def
execute
(
merge_request
)
merge_request
.
build_merge_train
(
user:
current_user
,
target_project:
merge_request
.
target_project
,
target_branch:
merge_request
.
target_branch
)
super
do
SystemNoteService
.
merge_train
(
merge_request
,
project
,
current_user
,
merge_request
.
merge_train
)
end
end
def
process
(
merge_request
)
return
unless
merge_request
.
on_train?
::
MergeTrains
::
RefreshMergeRequestsService
.
new
(
project
,
nil
).
execute
(
merge_request
)
end
def
cancel
(
merge_request
,
reason:
nil
,
refresh_next:
true
)
# Before dropping a merge request from a merge train, get the next
# merge request in order to refresh it later.
next_merge_request
=
merge_request
.
merge_train
&
.
next
if
refresh_next
super
(
merge_request
)
do
if
merge_request
.
merge_train
&
.
delete
SystemNoteService
.
cancel_merge_train
(
merge_request
,
project
,
current_user
,
reason:
reason
)
AutoMergeProcessWorker
.
perform_async
(
next_merge_request
.
id
)
if
next_merge_request
end
end
end
def
available_for?
(
merge_request
)
return
false
unless
merge_request
.
project
.
merge_trains_enabled?
return
false
if
merge_request
.
for_fork?
return
false
unless
merge_request
.
actual_head_pipeline
&
.
complete?
return
false
unless
merge_request
.
mergeable_state?
(
skip_ci_check:
true
)
true
end
end
end
ee/app/services/ee/auto_merge_service.rb
0 → 100644
View file @
0d880dbb
# frozen_string_literal: true
module
EE
module
AutoMergeService
extend
ActiveSupport
::
Concern
STRATEGY_MERGE_TRAIN
=
'merge_train'
.
freeze
EE_STRATEGIES
=
[
STRATEGY_MERGE_TRAIN
].
freeze
class_methods
do
extend
::
Gitlab
::
Utils
::
Override
include
::
Gitlab
::
Utils
::
StrongMemoize
override
:all_strategies
def
all_strategies
strong_memoize
(
:all_strategies
)
do
super
+
EE_STRATEGIES
end
end
end
end
end
ee/app/services/ee/system_note_service.rb
View file @
0d880dbb
...
...
@@ -189,5 +189,26 @@ module EE
def
change_epics_relation_act
(
subject_epic
,
user
,
action
,
text
,
text_params
)
create_note
(
NoteSummary
.
new
(
subject_epic
,
nil
,
user
,
text
%
text_params
,
action:
action
))
end
# Called when 'merge train' is executed
def
merge_train
(
noteable
,
project
,
author
,
merge_train
)
index
=
merge_train
.
index
body
=
if
index
==
0
'started a merge train'
else
"added this merge request to the merge train at index
#{
index
}
"
end
create_note
(
NoteSummary
.
new
(
noteable
,
project
,
author
,
body
,
action:
'merge'
))
end
# Called when 'merge train' is canceled
def
cancel_merge_train
(
noteable
,
project
,
author
,
reason:
nil
)
body
=
'removed this merge request from the merge train'
body
+=
" because
#{
reason
}
"
if
reason
create_note
(
NoteSummary
.
new
(
noteable
,
project
,
author
,
body
,
action:
'merge'
))
end
end
end
ee/app/services/merge_trains/create_pipeline_service.rb
0 → 100644
View file @
0d880dbb
# frozen_string_literal: true
module
MergeTrains
class
CreatePipelineService
<
BaseService
def
execute
(
merge_request
)
validation_status
=
validate
(
merge_request
)
return
validation_status
unless
validation_status
[
:status
]
==
:success
merge_status
=
create_merge_ref
(
merge_request
)
return
error
(
merge_status
[
:message
])
unless
merge_status
[
:status
]
==
:success
create_pipeline
(
merge_request
,
merge_status
)
end
private
def
validate
(
merge_request
)
return
error
(
'merge trains is disabled'
)
unless
merge_request
.
project
.
merge_trains_enabled?
return
error
(
'merge request is not on a merge train'
)
unless
merge_request
.
on_train?
return
error
(
'fork merge request is not supported'
)
if
merge_request
.
for_fork?
success
end
def
create_merge_ref
(
merge_request
)
::
MergeRequests
::
MergeToRefService
.
new
(
merge_request
.
project
,
merge_request
.
merge_user
).
execute
(
merge_request
)
end
def
create_pipeline
(
merge_request
,
merge_status
)
pipeline
=
::
Ci
::
CreatePipelineService
.
new
(
merge_request
.
source_project
,
merge_request
.
merge_user
,
ref:
merge_request
.
merge_ref_path
,
checkout_sha:
merge_status
[
:commit_id
],
target_sha:
merge_status
[
:target_id
],
source_sha:
merge_status
[
:source_id
])
.
execute
(
:merge_request_event
,
merge_request:
merge_request
)
return
error
(
pipeline
.
errors
.
full_messages
.
join
(
','
))
unless
pipeline
.
persisted?
success
(
pipeline:
pipeline
)
end
end
end
ee/app/services/merge_trains/refresh_merge_request_service.rb
0 → 100644
View file @
0d880dbb
# frozen_string_literal: true
module
MergeTrains
class
RefreshMergeRequestService
<
BaseService
include
Gitlab
::
Utils
::
StrongMemoize
ProcessError
=
Class
.
new
(
StandardError
)
attr_reader
:merge_request
##
# Arguments:
# merge_request ... The merge request to be refreshed
def
execute
(
merge_request
)
@merge_request
=
merge_request
validate!
create_pipeline!
if
should_create_pipeline?
merge!
if
should_merge?
success
rescue
ProcessError
=>
e
drop
(
e
)
end
private
def
validate!
unless
project
.
merge_trains_enabled?
&&
project
.
merge_pipelines_enabled?
raise
ProcessError
,
'project disabled merge trains'
end
unless
merge_request
.
on_train?
raise
ProcessError
,
'merge request is not on a merge train'
end
unless
merge_request
.
mergeable_state?
(
skip_ci_check:
true
)
raise
ProcessError
,
'merge request is not mergeable'
end
if
pipeline_for_merge_train
if
pipeline_for_merge_train
.
complete?
&&
!
pipeline_for_merge_train
.
success?
raise
ProcessError
,
'pipeline did not succeed'
end
end
end
def
should_create_pipeline?
first_in_train?
&&
(
pipeline_absent?
||
stale_pipeline?
)
end
def
create_pipeline!
result
=
MergeTrains
::
CreatePipelineService
.
new
(
merge_request
.
project
,
merge_user
)
.
execute
(
merge_request
)
raise
ProcessError
,
result
[
:message
]
unless
result
[
:status
]
==
:success
merge_train
.
update!
(
pipeline:
result
[
:pipeline
])
end
def
should_merge?
first_in_train?
&&
pipeline_for_merge_train
&
.
success?
end
def
merge!
MergeRequests
::
MergeService
.
new
(
project
,
merge_user
,
merge_request
.
merge_params
)
.
execute
(
merge_request
)
raise
ProcessError
,
'failed to merge'
unless
merge_request
.
merged?
merge_train
.
delete
end
def
stale_pipeline?
pipeline_for_merge_train
&&
!
pipeline_for_merge_train
.
latest_merge_request_pipeline?
end
def
pipeline_absent?
!
pipeline_for_merge_train
.
present?
end
def
merge_train
merge_request
.
merge_train
end
def
pipeline_for_merge_train
merge_train
.
pipeline
end
def
merge_user
merge_request
.
merge_user
end
def
first_in_train?
strong_memoize
(
:is_first_in_train
)
do
merge_train
.
first_in_train?
end
end
def
drop
(
error
)
AutoMerge
::
MergeTrainService
.
new
(
project
,
merge_user
)
.
cancel
(
merge_request
,
reason:
error
.
message
,
refresh_next:
false
)
error
(
error
.
message
)
end
end
end
ee/app/services/merge_trains/refresh_merge_requests_service.rb
0 → 100644
View file @
0d880dbb
# frozen_string_literal: true
module
MergeTrains
class
RefreshMergeRequestsService
<
BaseService
include
::
Gitlab
::
ExclusiveLeaseHelpers
##
# merge_request ... A merge request pointer in a merge train.
# All the merge requests following the specified merge request will be refreshed.
def
execute
(
merge_request
)
return
unless
merge_request
.
on_train?
in_lock
(
"merge_train:
#{
merge_request
.
target_project_id
}
-
#{
merge_request
.
target_branch
}
"
)
do
unsafe_refresh
(
merge_request
)
end
end
private
def
unsafe_refresh
(
merge_request
)
following_merge_requests_from
(
merge_request
).
each
do
|
merge_request
|
MergeTrains
::
RefreshMergeRequestService
.
new
(
merge_request
.
project
,
merge_request
.
merge_user
)
.
execute
(
merge_request
)
end
end
def
following_merge_requests_from
(
merge_request
)
merge_request
.
merge_train
.
all_next
.
to_a
.
unshift
(
merge_request
)
end
end
end
ee/changelogs/unreleased/add-merge-train-auto-merge-strategy.yml
0 → 100644
View file @
0d880dbb
---
title
:
Add Merge Train auto merge strategy
merge_request
:
13278
author
:
type
:
added
ee/spec/factories/merge_requests.rb
View file @
0d880dbb
...
...
@@ -13,8 +13,14 @@ FactoryBot.modify do
train_creator
{
author
}
end
auto_merge_enabled
true
auto_merge_strategy
AutoMergeService
::
STRATEGY_MERGE_TRAIN
merge_user
{
train_creator
}
after
:create
do
|
merge_request
,
evaluator
|
merge_request
.
get_on_train!
(
evaluator
.
train_creator
)
merge_request
.
create_merge_train
(
user:
evaluator
.
train_creator
,
target_project:
merge_request
.
target_project
,
target_branch:
merge_request
.
target_branch
)
end
end
...
...
ee/spec/features/merge_trains/two_merge_requests_on_train_spec.rb
0 → 100644
View file @
0d880dbb
# frozen_string_literal: true
require
'rails_helper'
describe
'Two merge requests on a merge train'
do
let
(
:project
)
{
create
(
:project
,
:repository
)
}
set
(
:maintainer_1
)
{
create
(
:user
)
}
set
(
:maintainer_2
)
{
create
(
:user
)
}
let
(
:merge_request_1
)
do
create
(
:merge_request
,
source_branch:
'feature'
,
source_project:
project
,
target_branch:
'master'
,
target_project:
project
,
merge_status:
'unchecked'
)
end
let
(
:merge_request_2
)
do
create
(
:merge_request
,
source_branch:
'signed-commits'
,
source_project:
project
,
target_branch:
'master'
,
target_project:
project
,
merge_status:
'unchecked'
)
end
let
(
:ci_yaml
)
do
{
test:
{
stage:
'test'
,
script:
'echo'
,
only:
[
'merge_requests'
]
}
}
end
before
do
project
.
add_maintainer
(
maintainer_1
)
project
.
add_maintainer
(
maintainer_2
)
stub_licensed_features
(
merge_pipelines:
true
,
merge_trains:
true
)
project
.
update!
(
merge_pipelines_enabled:
true
,
merge_trains_enabled:
true
)
stub_ci_pipeline_yaml_file
(
YAML
.
dump
(
ci_yaml
))
head_pipeline
=
double
(
'Ci::Pipeline'
)
allow
(
head_pipeline
).
to
receive
(
:complete?
)
{
true
}
allow
(
merge_request_1
).
to
receive
(
:actual_head_pipeline
)
{
head_pipeline
}
allow
(
merge_request_2
).
to
receive
(
:actual_head_pipeline
)
{
head_pipeline
}
AutoMergeService
.
new
(
project
,
maintainer_1
)
.
execute
(
merge_request_1
,
AutoMergeService
::
STRATEGY_MERGE_TRAIN
)
AutoMergeService
.
new
(
project
,
maintainer_2
)
.
execute
(
merge_request_2
,
AutoMergeService
::
STRATEGY_MERGE_TRAIN
)
merge_request_1
.
reload
merge_request_2
.
reload
end
it
'creates a pipeline for merge request 1'
do
expect
(
merge_request_1
.
merge_train
.
pipeline
).
to
be_merge_request_pipeline
expect
(
merge_request_1
.
merge_train
.
pipeline
.
user
).
to
eq
(
maintainer_1
)
end
it
'does not create a pipeline for merge request 2'
do
expect
(
merge_request_2
.
merge_train
.
pipeline
).
to
be_nil
end
it
'does not merge anything yet'
do
expect
(
merge_request_1
).
to
be_opened
expect
(
merge_request_2
).
to
be_opened
end
context
'when the pipeline for merge request 1 succeeded'
do
before
do
merge_request_1
.
merge_train
.
pipeline
.
succeed!
merge_request_1
.
reload
merge_request_2
.
reload
end
it
'merges merge request 1'
do
expect
(
merge_request_1
).
to
be_merged
expect
(
merge_request_1
.
metrics
.
merged_by
).
to
eq
(
maintainer_1
)
end
it
'removes merge request 1 from the merge train'
do
expect
(
merge_request_1
.
merge_train
).
to
be_nil
end
it
'creates a pipeline for merge request 2'
do
expect
(
merge_request_2
.
merge_train
.
pipeline
).
to
be_merge_request_pipeline
expect
(
merge_request_2
.
merge_train
.
pipeline
.
user
).
to
eq
(
maintainer_2
)
end
context
'when the pipeline for merge request 2 succeeded'
do
before
do
merge_request_2
.
merge_train
.
pipeline
.
succeed!
merge_request_2
.
reload
end
it
'merges merge request 2'
do
expect
(
merge_request_2
).
to
be_merged
expect
(
merge_request_2
.
metrics
.
merged_by
).
to
eq
(
maintainer_2
)
end
it
'removes merge request 2 from the merge train'
do
expect
(
merge_request_2
.
merge_train
).
to
be_nil
end
end
end
context
'when the pipeline for merge request 1 failed'
do
before
do
merge_request_1
.
merge_train
.
pipeline
.
drop!
merge_request_1
.
reload
merge_request_2
.
reload
end
it
'does not merges merge request 1'
do
expect
(
merge_request_1
).
to
be_opened
end
it
'drops merge request 1 from the merge train'
do
expect
(
merge_request_1
.
merge_train
).
to
be_nil
expect
(
merge_request_1
.
notes
.
last
.
note
).
to
eq
(
'removed this merge request from the merge train because pipeline did not succeed'
)
end
it
'creates a pipeline for merge request 2'
do
expect
(
merge_request_2
.
merge_train
.
pipeline
).
to
be_merge_request_pipeline
expect
(
merge_request_2
.
merge_train
.
pipeline
.
user
).
to
eq
(
maintainer_2
)
end
end
context
'when merge request 1 is canceled by a user'
do
before
do
AutoMergeService
.
new
(
project
,
maintainer_1
).
cancel
(
merge_request_1
)
merge_request_1
.
reload
merge_request_2
.
reload
end
it
'drops merge request 1 from the merge train'
do
expect
(
merge_request_1
.
merge_train
).
to
be_nil
expect
(
merge_request_1
.
notes
.
last
.
note
).
to
eq
(
'removed this merge request from the merge train'
)
end
it
'creates a pipeline for merge request 2'
do
expect
(
merge_request_2
.
merge_train
.
pipeline
).
to
be_merge_request_pipeline
expect
(
merge_request_2
.
merge_train
.
pipeline
.
user
).
to
eq
(
maintainer_2
)
end
end
context
'when merge request 1 is not mergeable'
do
before
do
merge_request_1
.
update!
(
title:
merge_request_1
.
wip_title
)
merge_request_1
.
merge_train
.
pipeline
.
succeed!
merge_request_1
.
reload
merge_request_2
.
reload
end
it
'drops merge request 1 from the merge train'
do
expect
(
merge_request_1
.
merge_train
).
to
be_nil
expect
(
merge_request_1
.
notes
.
last
.
note
).
to
eq
(
'removed this merge request from the merge train because merge request is not mergeable'
)
end
it
'creates a pipeline for merge request 2'
do
expect
(
merge_request_2
.
merge_train
.
pipeline
).
to
be_merge_request_pipeline
expect
(
merge_request_2
.
merge_train
.
pipeline
.
user
).
to
eq
(
maintainer_2
)
end
end
context
'when merge trains option is disabled'
do
before
do
project
.
update!
(
merge_trains_enabled:
false
)
merge_request_1
.
merge_train
.
pipeline
.
succeed!
merge_request_1
.
reload
merge_request_2
.
reload
end
it
'drops merge request 1 from the merge train'
do
expect
(
merge_request_1
.
merge_train
).
to
be_nil
expect
(
merge_request_1
.
notes
.
last
.
note
).
to
eq
(
'removed this merge request from the merge train because project disabled merge trains'
)
end
it
'drops merge request 2 from the merge train'
do
expect
(
merge_request_2
.
merge_train
).
to
be_nil
expect
(
merge_request_2
.
notes
.
last
.
note
).
to
eq
(
'removed this merge request from the merge train because project disabled merge trains'
)
end
after
do
project
.
update!
(
merge_trains_enabled:
true
)
end
end
end
ee/spec/models/merge_request_spec.rb
View file @
0d880dbb
...
...
@@ -622,46 +622,6 @@ describe MergeRequest do
end
end
describe
'#get_on_train!'
do
subject
{
merge_request
.
get_on_train!
(
user
)
}
let
(
:user
)
{
create
(
:user
)
}
it
'gets on the train'
do
expect
{
subject
}.
to
change
{
MergeTrain
.
count
}.
by
(
1
)
end
context
'when the merge request is already on a merge train'
do
before
do
merge_request
.
get_on_train!
(
user
)
end
it
'raises an exception'
do
expect
{
merge_request
.
get_on_train!
(
user
)
}.
to
raise_exception
(
ActiveRecord
::
RecordNotUnique
)
end
end
end
describe
'#get_off_train!'
do
subject
{
merge_request
.
get_off_train!
}
let!
(
:merge_request
)
do
create
(
:merge_request
,
:on_train
,
source_project:
project
,
target_project:
project
)
end
it
'gets off from the train'
do
expect
{
subject
}.
to
change
{
MergeTrain
.
count
}.
by
(
-
1
)
end
context
'when the merge request is not on a merge train yet'
do
let
(
:merge_request
)
{
create
(
:merge_request
,
source_project:
project
,
target_project:
project
)
}
it
'raises an exception'
do
expect
{
subject
}.
to
raise_exception
(
NoMethodError
)
end
end
end
describe
'#on_train?'
do
subject
{
merge_request
.
on_train?
}
...
...
ee/spec/models/merge_train_spec.rb
View file @
0d880dbb
...
...
@@ -11,6 +11,10 @@ describe MergeTrain do
it
{
is_expected
.
to
belong_to
(
:user
)
}
it
{
is_expected
.
to
belong_to
(
:pipeline
)
}
before
do
allow
(
AutoMergeProcessWorker
).
to
receive
(
:perform_async
)
end
describe
'.all_in_train'
do
subject
{
described_class
.
all_in_train
(
merge_request
)
}
...
...
@@ -108,6 +112,27 @@ describe MergeTrain do
end
end
describe
'#next'
do
subject
{
merge_train
.
next
}
let
(
:merge_train
)
{
merge_request
.
merge_train
}
let!
(
:merge_request
)
{
create_merge_request_on_train
}
context
'when the merge request is at last on the train'
do
it
'returns nil'
do
is_expected
.
to
be_nil
end
end
context
'when the other merge request is on the merge train'
do
let!
(
:merge_request_2
)
{
create_merge_request_on_train
(
source_branch:
'improve/awesome'
)
}
it
'returns the next merge request'
do
is_expected
.
to
eq
(
merge_request_2
)
end
end
end
describe
'#first_in_train?'
do
subject
{
merge_train
.
first_in_train?
}
...
...
ee/spec/services/auto_merge/merge_train_service_spec.rb
0 → 100644
View file @
0d880dbb
# frozen_string_literal: true
require
'spec_helper'
describe
AutoMerge
::
MergeTrainService
do
include
ExclusiveLeaseHelpers
set
(
:project
)
{
create
(
:project
,
:repository
)
}
set
(
:user
)
{
create
(
:user
)
}
let
(
:service
)
{
described_class
.
new
(
project
,
user
,
params
)
}
let
(
:params
)
{
{}
}
let
(
:merge_request
)
do
create
(
:merge_request
,
:with_merge_request_pipeline
,
source_project:
project
,
target_project:
project
)
end
before
do
project
.
add_maintainer
(
user
)
allow
(
AutoMergeProcessWorker
).
to
receive
(
:perform_async
)
{
}
stub_licensed_features
(
merge_trains:
true
,
merge_pipelines:
true
)
project
.
update!
(
merge_trains_enabled:
true
,
merge_pipelines_enabled:
true
)
end
describe
'#execute'
do
subject
{
service
.
execute
(
merge_request
)
}
it
'enables auto merge on the merge request'
do
subject
merge_request
.
reload
expect
(
merge_request
.
auto_merge_enabled
).
to
be_truthy
expect
(
merge_request
.
merge_user
).
to
eq
(
user
)
expect
(
merge_request
.
auto_merge_strategy
).
to
eq
(
AutoMergeService
::
STRATEGY_MERGE_TRAIN
)
end
it
'creates merge train'
do
subject
merge_request
.
reload
expect
(
merge_request
.
merge_train
).
to
be_present
expect
(
merge_request
.
merge_train
.
user
).
to
eq
(
user
)
end
it
'creates system note'
do
expect
(
SystemNoteService
)
.
to
receive
(
:merge_train
).
with
(
merge_request
,
project
,
user
,
instance_of
(
MergeTrain
))
subject
end
it
'returns result code'
do
is_expected
.
to
eq
(
:merge_train
)
end
context
'when failed to save the record'
do
before
do
allow
(
merge_request
).
to
receive
(
:save
)
{
false
}
end
it
'returns result code'
do
is_expected
.
to
eq
(
:failed
)
end
end
end
describe
'#process'
do
subject
{
service
.
process
(
merge_request
)
}
let
(
:merge_request
)
do
create
(
:merge_request
,
:on_train
,
source_project:
project
,
source_branch:
'feature'
,
target_project:
project
,
target_branch:
'master'
)
end
it
'calls RefreshMergeRequestsService'
do
expect_next_instance_of
(
MergeTrains
::
RefreshMergeRequestsService
)
do
|
service
|
expect
(
service
).
to
receive
(
:execute
).
with
(
merge_request
)
end
subject
end
context
'when merge request is not on a merge train'
do
let
(
:merge_request
)
{
create
(
:merge_request
)
}
it
'does not call RefreshMergeRequestsService'
do
expect
(
MergeTrains
::
RefreshMergeRequestsService
).
not_to
receive
(
:new
)
subject
end
end
end
describe
'#cancel'
do
subject
{
service
.
cancel
(
merge_request
,
**
params
)
}
let
(
:params
)
{
{}
}
let!
(
:merge_request
)
do
create
(
:merge_request
,
:on_train
,
source_project:
project
,
source_branch:
'feature'
,
target_project:
project
,
target_branch:
'master'
)
end
it
'cancels auto merge on the merge request'
do
subject
merge_request
.
reload
expect
(
merge_request
).
not_to
be_auto_merge_enabled
expect
(
merge_request
.
merge_user
).
to
be_nil
expect
(
merge_request
.
merge_params
).
not_to
include
(
'should_remove_source_branch'
)
expect
(
merge_request
.
merge_params
).
not_to
include
(
'commit_message'
)
expect
(
merge_request
.
merge_params
).
not_to
include
(
'squash_commit_message'
)
expect
(
merge_request
.
merge_params
).
not_to
include
(
'auto_merge_strategy'
)
expect
(
merge_request
.
merge_train
).
not_to
be_present
end
it
'writes system note to the merge request'
do
expect
(
SystemNoteService
)
.
to
receive
(
:cancel_merge_train
).
with
(
merge_request
,
project
,
user
,
anything
)
subject
end
context
'when reason is specified'
do
let
(
:params
)
{
{
reason:
'Pipeline failed'
}
}
it
'passes the reason to SystemNoteService'
do
expect
(
SystemNoteService
)
.
to
receive
(
:cancel_merge_train
).
with
(
any_args
,
reason:
'Pipeline failed'
)
subject
end
end
context
'when the other merge request is following the merge request'
do
let!
(
:merge_request_2
)
do
create
(
:merge_request
,
:on_train
,
source_project:
project
,
source_branch:
'signed-commits'
,
target_project:
project
,
target_branch:
'master'
)
end
it
'processes the next merge request on the train by default'
do
expect
(
AutoMergeProcessWorker
).
to
receive
(
:perform_async
).
with
(
merge_request_2
.
id
)
subject
end
context
'when refresh next is false'
do
let
(
:params
)
{
{
refresh_next:
false
}
}
it
'does not process the next merge request on the train'
do
expect
(
AutoMergeProcessWorker
).
not_to
receive
(
:perform_async
).
with
(
merge_request_2
.
id
)
subject
end
end
end
end
describe
'#available_for?'
do
subject
{
service
.
available_for?
(
merge_request
)
}
let
(
:pipeline
)
{
double
}
before
do
allow
(
merge_request
).
to
receive
(
:mergeable_state?
)
{
true
}
allow
(
merge_request
).
to
receive
(
:for_fork?
)
{
false
}
allow
(
merge_request
).
to
receive
(
:actual_head_pipeline
)
{
pipeline
}
allow
(
pipeline
).
to
receive
(
:complete?
)
{
true
}
end
it
{
is_expected
.
to
be_truthy
}
context
'when merge trains project option is disabled'
do
before
do
project
.
update!
(
merge_trains_enabled:
false
)
end
it
{
is_expected
.
to
be_falsy
}
after
do
project
.
update!
(
merge_trains_enabled:
true
)
end
end
context
'when merge request is not mergeable'
do
before
do
allow
(
merge_request
).
to
receive
(
:mergeable_state?
)
{
false
}
end
it
{
is_expected
.
to
be_falsy
}
end
context
'when merge request is submitted from a forked project'
do
before
do
allow
(
merge_request
).
to
receive
(
:for_fork?
)
{
true
}
end
it
{
is_expected
.
to
be_falsy
}
end
context
'when the head pipeline of the merge request has not finished'
do
before
do
allow
(
pipeline
).
to
receive
(
:complete?
)
{
false
}
end
it
{
is_expected
.
to
be_falsy
}
end
end
def
create_pipeline_for
(
merge_request
)
MergeRequests
::
CreatePipelineService
.
new
(
project
,
user
).
execute
(
merge_request
)
end
end
ee/spec/services/ee/auto_merge_service_spec.rb
0 → 100644
View file @
0d880dbb
# frozen_string_literal: true
require
'spec_helper'
describe
AutoMergeService
do
describe
'.all_strategies'
do
subject
{
described_class
.
all_strategies
}
it
'includes all strategies'
do
is_expected
.
to
include
(
AutoMergeService
::
STRATEGY_MERGE_TRAIN
)
end
end
end
ee/spec/services/merge_trains/create_pipeline_service_spec.rb
0 → 100644
View file @
0d880dbb
# frozen_string_literal: true
require
'spec_helper'
describe
MergeTrains
::
CreatePipelineService
do
set
(
:project
)
{
create
(
:project
,
:repository
)
}
set
(
:maintainer
)
{
create
(
:user
)
}
let
(
:service
)
{
described_class
.
new
(
project
,
maintainer
)
}
before
do
project
.
add_maintainer
(
maintainer
)
stub_licensed_features
(
merge_pipelines:
true
,
merge_trains:
true
)
project
.
update!
(
merge_pipelines_enabled:
true
,
merge_trains_enabled:
true
)
end
describe
'#execute'
do
subject
{
service
.
execute
(
merge_request
)
}
let!
(
:merge_request
)
do
create
(
:merge_request
,
:on_train
,
train_creator:
maintainer
,
source_branch:
'feature'
,
source_project:
project
,
target_branch:
'master'
,
target_project:
project
,
merge_status:
'unchecked'
)
end
shared_examples_for
'returns an error'
do
let
(
:expected_reason
)
{
'unknown'
}
it
do
expect
(
subject
[
:status
]).
to
eq
(
:error
)
expect
(
subject
[
:message
]).
to
eq
(
expected_reason
)
end
end
context
'when merge trains option is disabled'
do
before
do
project
.
update!
(
merge_trains_enabled:
false
)
end
it_behaves_like
'returns an error'
do
let
(
:expected_reason
)
{
'merge trains is disabled'
}
end
after
do
project
.
update!
(
merge_trains_enabled:
true
)
end
end
context
'when merge request is not on a merge train'
do
let!
(
:merge_request
)
do
create
(
:merge_request
,
source_branch:
'feature'
,
source_project:
project
,
target_branch:
'master'
,
target_project:
project
)
end
it_behaves_like
'returns an error'
do
let
(
:expected_reason
)
{
'merge request is not on a merge train'
}
end
end
context
'when merge request is submitted from a forked project'
do
before
do
allow
(
merge_request
).
to
receive
(
:for_fork?
)
{
true
}
end
it_behaves_like
'returns an error'
do
let
(
:expected_reason
)
{
'fork merge request is not supported'
}
end
end
context
'when prepared merge ref successfully'
do
context
'when .gitlab-ci.yml has only: [merge_requests] specification'
do
let
(
:ci_yaml
)
do
{
test:
{
stage:
'test'
,
script:
'echo'
,
only:
[
'merge_requests'
]
}
}
end
before
do
stub_ci_pipeline_yaml_file
(
YAML
.
dump
(
ci_yaml
))
end
it
'calls Ci::CreatePipelineService'
do
expect_next_instance_of
(
Ci
::
CreatePipelineService
,
project
,
maintainer
,
any_args
)
do
|
pipeline_service
|
expect
(
pipeline_service
).
to
receive
(
:execute
)
.
with
(
:merge_request_event
,
hash_including
(
merge_request:
merge_request
)).
and_call_original
end
subject
end
end
context
'when .gitlab-ci.yml does not have only: [merge_requests] specification'
do
it_behaves_like
'returns an error'
do
let
(
:expected_reason
)
{
'No stages / jobs for this pipeline.'
}
end
end
end
context
'when failed to prepare merge ref'
do
before
do
check_service
=
double
allow
(
::
MergeRequests
::
MergeToRefService
).
to
receive
(
:new
)
{
check_service
}
allow
(
check_service
).
to
receive
(
:execute
)
{
{
status: :error
,
message:
'Merge ref was not found'
}
}
end
it_behaves_like
'returns an error'
do
let
(
:expected_reason
)
{
'Merge ref was not found'
}
end
end
end
end
ee/spec/services/merge_trains/refresh_merge_request_service_spec.rb
0 → 100644
View file @
0d880dbb
# frozen_string_literal: true
require
'spec_helper'
describe
MergeTrains
::
RefreshMergeRequestService
do
set
(
:project
)
{
create
(
:project
,
:repository
)
}
set
(
:maintainer
)
{
create
(
:user
)
}
let
(
:service
)
{
described_class
.
new
(
project
,
maintainer
)
}
before
do
project
.
add_maintainer
(
maintainer
)
stub_licensed_features
(
merge_pipelines:
true
,
merge_trains:
true
)
project
.
update!
(
merge_pipelines_enabled:
true
,
merge_trains_enabled:
true
)
end
describe
'#execute'
do
subject
{
service
.
execute
(
merge_request
)
}
let!
(
:merge_request
)
do
create
(
:merge_request
,
:on_train
,
train_creator:
maintainer
,
source_branch:
'feature'
,
source_project:
project
,
target_branch:
'master'
,
target_project:
project
)
end
shared_examples_for
'drops the merge request from the merge train'
do
let
(
:expected_reason
)
{
'unknown'
}
it
do
expect_next_instance_of
(
AutoMerge
::
MergeTrainService
)
do
|
service
|
expect
(
service
).
to
receive
(
:cancel
).
with
(
merge_request
,
hash_including
(
reason:
expected_reason
))
end
subject
end
end
context
'when merge train project configuration is disabled'
do
before
do
project
.
update!
(
merge_trains_enabled:
false
)
end
it_behaves_like
'drops the merge request from the merge train'
do
let
(
:expected_reason
)
{
'project disabled merge trains'
}
end
after
do
project
.
update!
(
merge_trains_enabled:
true
)
end
end
context
'when merge request is not under a mergeable state'
do
before
do
merge_request
.
update!
(
title:
merge_request
.
wip_title
)
end
it_behaves_like
'drops the merge request from the merge train'
do
let
(
:expected_reason
)
{
'merge request is not mergeable'
}
end
end
context
'when pipeline for merge train failed'
do
let
(
:pipeline
)
{
create
(
:ci_pipeline
,
:failed
)
}
before
do
merge_request
.
merge_train
.
update!
(
pipeline:
pipeline
)
end
it_behaves_like
'drops the merge request from the merge train'
do
let
(
:expected_reason
)
{
'pipeline did not succeed'
}
end
end
context
'when pipeline has not been created yet'
do
context
'when the merge request is the first queue'
do
it
'creates a pipeline for merge train'
do
expect_next_instance_of
(
MergeTrains
::
CreatePipelineService
,
project
,
maintainer
)
do
|
pipeline_service
|
expect
(
pipeline_service
).
to
receive
(
:execute
).
with
(
merge_request
).
and_call_original
end
subject
end
context
'when it failed to create a pipeline'
do
before
do
allow_any_instance_of
(
MergeTrains
::
CreatePipelineService
).
to
receive
(
:execute
)
{
{
result: :error
,
message:
'failed to create pipeline'
}
}
end
it_behaves_like
'drops the merge request from the merge train'
do
let
(
:expected_reason
)
{
'failed to create pipeline'
}
end
end
end
context
'when the merge request is not the first queue'
do
before
do
allow
(
merge_request
.
merge_train
).
to
receive
(
:first_in_train?
)
{
false
}
end
it
'does not create a pipeline for merge train'
do
expect
(
MergeTrains
::
CreatePipelineService
).
not_to
receive
(
:new
)
subject
end
end
end
context
'when pipeline for merge train succeeded'
do
let
(
:pipeline
)
{
create
(
:ci_pipeline
,
:success
)
}
before
do
allow
(
pipeline
).
to
receive
(
:latest_merge_request_pipeline?
)
{
true
}
merge_request
.
merge_train
.
update!
(
pipeline:
pipeline
)
end
context
'when the merge request is the first queue'
do
it
'merges the merge request'
do
expect_next_instance_of
(
MergeRequests
::
MergeService
,
project
,
maintainer
,
anything
)
do
|
service
|
expect
(
service
).
to
receive
(
:execute
).
with
(
merge_request
)
end
subject
end
context
'when it failed to merge the merge request'
do
before
do
allow_any_instance_of
(
MergeRequests
::
MergeService
).
to
receive
(
:execute
)
{
{
result: :error
}
}
end
it_behaves_like
'drops the merge request from the merge train'
do
let
(
:expected_reason
)
{
'failed to merge'
}
end
end
end
context
'when the merge request is not the first queue'
do
before
do
allow
(
merge_request
.
merge_train
).
to
receive
(
:first_in_train?
)
{
false
}
end
it
'does not merge the merge request'
do
expect
(
MergeRequests
::
MergeService
).
not_to
receive
(
:new
)
subject
end
end
end
end
end
ee/spec/services/merge_trains/refresh_merge_requests_service_spec.rb
0 → 100644
View file @
0d880dbb
# frozen_string_literal: true
require
'spec_helper'
describe
MergeTrains
::
RefreshMergeRequestsService
do
include
ExclusiveLeaseHelpers
set
(
:project
)
{
create
(
:project
)
}
set
(
:maintainer_1
)
{
create
(
:user
)
}
set
(
:maintainer_2
)
{
create
(
:user
)
}
let
(
:service
)
{
described_class
.
new
(
project
,
maintainer_1
)
}
before
do
project
.
add_maintainer
(
maintainer_1
)
project
.
add_maintainer
(
maintainer_2
)
end
describe
'#execute'
do
subject
{
service
.
execute
(
merge_request
)
}
let!
(
:merge_request_1
)
do
create
(
:merge_request
,
:on_train
,
train_creator:
maintainer_1
,
source_branch:
'feature'
,
source_project:
project
,
target_branch:
'master'
,
target_project:
project
)
end
let!
(
:merge_request_2
)
do
create
(
:merge_request
,
:on_train
,
train_creator:
maintainer_2
,
source_branch:
'signed-commits'
,
source_project:
project
,
target_branch:
'master'
,
target_project:
project
)
end
let
(
:refresh_service_1
)
{
double
}
let
(
:refresh_service_2
)
{
double
}
before
do
allow
(
MergeTrains
::
RefreshMergeRequestService
)
.
to
receive
(
:new
).
with
(
project
,
maintainer_1
)
{
refresh_service_1
}
allow
(
MergeTrains
::
RefreshMergeRequestService
)
.
to
receive
(
:new
).
with
(
project
,
maintainer_2
)
{
refresh_service_2
}
end
context
'when merge request 1 is passed'
do
let
(
:merge_request
)
{
merge_request_1
}
it
'executes RefreshMergeRequestService to all the following merge requests'
do
expect
(
refresh_service_1
).
to
receive
(
:execute
).
with
(
merge_request_1
)
expect
(
refresh_service_2
).
to
receive
(
:execute
).
with
(
merge_request_2
)
subject
end
context
'when merge request 1 is not on a merge train'
do
let
(
:merge_request
)
{
merge_request_1
}
let!
(
:merge_request_1
)
{
create
(
:merge_request
)
}
it
'does not refresh'
do
expect
(
refresh_service_1
).
not_to
receive
(
:execute
).
with
(
merge_request_1
)
subject
end
end
context
'when the exlusive lock has already been taken'
do
let
(
:lease_key
)
do
"merge_train:
#{
merge_request_1
.
target_project_id
}
-
#{
merge_request_1
.
target_branch
}
"
end
before
do
stub_exclusive_lease_taken
(
lease_key
)
end
it
'raises FailedToObtainLockError'
do
expect
{
subject
}.
to
raise_error
(
Gitlab
::
ExclusiveLeaseHelpers
::
FailedToObtainLockError
)
end
end
end
context
'when merge request 2 is passed'
do
let
(
:merge_request
)
{
merge_request_2
}
it
'executes RefreshMergeRequestService to all the following merge requests'
do
expect
(
refresh_service_1
).
not_to
receive
(
:execute
).
with
(
merge_request_1
)
expect
(
refresh_service_2
).
to
receive
(
:execute
).
with
(
merge_request_2
)
subject
end
end
end
end
ee/spec/services/system_note_service_spec.rb
View file @
0d880dbb
...
...
@@ -323,4 +323,51 @@ describe SystemNoteService do
end
end
end
describe
'.merge_train'
do
subject
{
described_class
.
merge_train
(
noteable
,
project
,
author
,
noteable
.
merge_train
)
}
let
(
:noteable
)
{
create
(
:merge_request
,
:on_train
,
source_project:
project
,
target_project:
project
)
}
it_behaves_like
'a system note'
do
let
(
:action
)
{
'merge'
}
end
it
"posts the 'merge train' system note"
do
expect
(
subject
.
note
).
to
eq
(
'started a merge train'
)
end
context
'when index of the merge request is not zero'
do
before
do
allow
(
noteable
.
merge_train
).
to
receive
(
:index
)
{
1
}
end
it
"posts the 'merge train' system note"
do
expect
(
subject
.
note
).
to
eq
(
'added this merge request to the merge train at index 1'
)
end
end
end
describe
'.cancel_merge_train'
do
subject
{
described_class
.
cancel_merge_train
(
noteable
,
project
,
author
,
reason:
reason
)
}
let
(
:noteable
)
{
create
(
:merge_request
,
:on_train
,
source_project:
project
,
target_project:
project
)
}
let
(
:reason
)
{
}
it_behaves_like
'a system note'
do
let
(
:action
)
{
'merge'
}
end
it
"posts the 'merge train' system note"
do
expect
(
subject
.
note
).
to
eq
(
'removed this merge request from the merge train'
)
end
context
'when reason is specified'
do
let
(
:reason
)
{
'merge request is not mergeable'
}
it
"posts the 'merge train' system note"
do
expect
(
subject
.
note
).
to
eq
(
'removed this merge request from the merge train because merge request is not mergeable'
)
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