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
c1977621
Commit
c1977621
authored
Jan 21, 2020
by
Shinya Maeda
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Fix a race condition on merge train
This commit fixes the race condition on merge train
parent
47db8303
Changes
22
Expand all
Hide whitespace changes
Inline
Side-by-side
Showing
22 changed files
with
245 additions
and
242 deletions
+245
-242
app/workers/all_queues.yml
app/workers/all_queues.yml
+1
-1
app/workers/auto_merge_process_worker.rb
app/workers/auto_merge_process_worker.rb
+1
-0
changelogs/unreleased/fix-merge-train-race-condition.yml
changelogs/unreleased/fix-merge-train-race-condition.yml
+5
-0
db/migrate/20200128105731_add_duration_to_merge_trains.rb
db/migrate/20200128105731_add_duration_to_merge_trains.rb
+10
-0
db/schema.rb
db/schema.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
+58
-25
ee/app/services/auto_merge/merge_train_service.rb
ee/app/services/auto_merge/merge_train_service.rb
+2
-2
ee/app/services/ee/merge_requests/refresh_service.rb
ee/app/services/ee/merge_requests/refresh_service.rb
+2
-0
ee/app/services/merge_trains/check_status_service.rb
ee/app/services/merge_trains/check_status_service.rb
+1
-1
ee/app/services/merge_trains/refresh_merge_request_service.rb
...pp/services/merge_trains/refresh_merge_request_service.rb
+7
-34
ee/app/services/merge_trains/refresh_merge_requests_service.rb
...p/services/merge_trains/refresh_merge_requests_service.rb
+1
-15
ee/spec/factories/merge_requests.rb
ee/spec/factories/merge_requests.rb
+1
-1
ee/spec/factories/merge_trains.rb
ee/spec/factories/merge_trains.rb
+9
-5
ee/spec/features/merge_trains/two_merge_requests_on_train_spec.rb
...features/merge_trains/two_merge_requests_on_train_spec.rb
+1
-1
ee/spec/models/merge_request_spec.rb
ee/spec/models/merge_request_spec.rb
+3
-29
ee/spec/models/merge_train_spec.rb
ee/spec/models/merge_train_spec.rb
+86
-59
ee/spec/services/auto_merge/merge_train_service_spec.rb
ee/spec/services/auto_merge/merge_train_service_spec.rb
+18
-2
ee/spec/services/ee/merge_requests/refresh_service_spec.rb
ee/spec/services/ee/merge_requests/refresh_service_spec.rb
+10
-0
ee/spec/services/merge_trains/check_status_service_spec.rb
ee/spec/services/merge_trains/check_status_service_spec.rb
+5
-5
ee/spec/services/merge_trains/refresh_merge_request_service_spec.rb
...rvices/merge_trains/refresh_merge_request_service_spec.rb
+20
-32
ee/spec/services/merge_trains/refresh_merge_requests_service_spec.rb
...vices/merge_trains/refresh_merge_requests_service_spec.rb
+2
-22
No files found.
app/workers/all_queues.yml
View file @
c1977621
...
@@ -13,7 +13,7 @@
...
@@ -13,7 +13,7 @@
:feature_category: :continuous_delivery
:feature_category: :continuous_delivery
:has_external_dependencies:
:has_external_dependencies:
:latency_sensitive:
:latency_sensitive:
:resource_boundary: :
unknown
:resource_boundary: :
cpu
:weight:
3
:weight:
3
-
:name: chaos:chaos_cpu_spin
-
:name: chaos:chaos_cpu_spin
:feature_category: :chaos_engineering
:feature_category: :chaos_engineering
...
...
app/workers/auto_merge_process_worker.rb
View file @
c1977621
...
@@ -5,6 +5,7 @@ class AutoMergeProcessWorker
...
@@ -5,6 +5,7 @@ class AutoMergeProcessWorker
queue_namespace
:auto_merge
queue_namespace
:auto_merge
feature_category
:continuous_delivery
feature_category
:continuous_delivery
worker_resource_boundary
:cpu
def
perform
(
merge_request_id
)
def
perform
(
merge_request_id
)
MergeRequest
.
find_by_id
(
merge_request_id
).
try
do
|
merge_request
|
MergeRequest
.
find_by_id
(
merge_request_id
).
try
do
|
merge_request
|
...
...
changelogs/unreleased/fix-merge-train-race-condition.yml
0 → 100644
View file @
c1977621
---
title
:
Use state machine in Merge Train to avoid race conditions
merge_request
:
23395
author
:
type
:
fixed
db/migrate/20200128105731_add_duration_to_merge_trains.rb
0 → 100644
View file @
c1977621
# frozen_string_literal: true
class
AddDurationToMergeTrains
<
ActiveRecord
::
Migration
[
5.2
]
DOWNTIME
=
false
def
change
add_column
:merge_trains
,
:merged_at
,
:datetime_with_timezone
add_column
:merge_trains
,
:duration
,
:integer
end
end
db/schema.rb
View file @
c1977621
...
@@ -2624,6 +2624,8 @@ ActiveRecord::Schema.define(version: 2020_01_29_035708) do
...
@@ -2624,6 +2624,8 @@ ActiveRecord::Schema.define(version: 2020_01_29_035708) do
t
.
integer
"target_project_id"
,
null:
false
t
.
integer
"target_project_id"
,
null:
false
t
.
text
"target_branch"
,
null:
false
t
.
text
"target_branch"
,
null:
false
t
.
integer
"status"
,
limit:
2
,
default:
0
,
null:
false
t
.
integer
"status"
,
limit:
2
,
default:
0
,
null:
false
t
.
datetime_with_timezone
"merged_at"
t
.
integer
"duration"
t
.
index
[
"merge_request_id"
],
name:
"index_merge_trains_on_merge_request_id"
,
unique:
true
t
.
index
[
"merge_request_id"
],
name:
"index_merge_trains_on_merge_request_id"
,
unique:
true
t
.
index
[
"pipeline_id"
],
name:
"index_merge_trains_on_pipeline_id"
t
.
index
[
"pipeline_id"
],
name:
"index_merge_trains_on_pipeline_id"
t
.
index
[
"target_project_id"
,
"target_branch"
,
"status"
],
name:
"index_for_status_per_branch_per_project"
t
.
index
[
"target_project_id"
,
"target_branch"
,
"status"
],
name:
"index_for_status_per_branch_per_project"
...
...
ee/app/models/ee/merge_request.rb
View file @
c1977621
...
@@ -62,14 +62,6 @@ module EE
...
@@ -62,14 +62,6 @@ module EE
accepts_nested_attributes_for
:approval_rules
,
allow_destroy:
true
accepts_nested_attributes_for
:approval_rules
,
allow_destroy:
true
state_machine
:state_id
do
after_transition
any
=>
:merged
do
|
merge_request
|
merge_request
.
merge_train
&
.
merged!
true
end
end
scope
:order_review_time_desc
,
->
do
scope
:order_review_time_desc
,
->
do
joins
(
:metrics
).
reorder
(
::
Gitlab
::
Database
.
nulls_last_order
(
'merge_request_metrics.first_comment_at'
))
joins
(
:metrics
).
reorder
(
::
Gitlab
::
Database
.
nulls_last_order
(
'merge_request_metrics.first_comment_at'
))
end
end
...
...
ee/app/models/merge_train.rb
View file @
c1977621
...
@@ -3,16 +3,14 @@
...
@@ -3,16 +3,14 @@
class
MergeTrain
<
ApplicationRecord
class
MergeTrain
<
ApplicationRecord
include
AfterCommitQueue
include
AfterCommitQueue
ACTIVE_STATUSES
=
%w[created stale fresh]
.
freeze
ACTIVE_STATUSES
=
%w[idle stale fresh]
.
freeze
COMPLETE_STATUSES
=
%w[merged merging]
.
freeze
belongs_to
:target_project
,
class_name:
"Project"
belongs_to
:target_project
,
class_name:
"Project"
belongs_to
:merge_request
,
inverse_of: :merge_train
belongs_to
:merge_request
,
inverse_of: :merge_train
belongs_to
:user
belongs_to
:user
belongs_to
:pipeline
,
class_name:
'Ci::Pipeline'
belongs_to
:pipeline
,
class_name:
'Ci::Pipeline'
after_commit
:cleanup_ref
,
if:
->
{
saved_change_to_status?
&&
merged?
}
after_commit
:refresh_async
,
if:
->
{
saved_change_to_status?
&&
stale?
}
after_destroy
do
|
merge_train
|
after_destroy
do
|
merge_train
|
run_after_commit
do
run_after_commit
do
merge_train
.
pipeline
&
.
cancel_running
(
retries:
1
)
merge_train
.
pipeline
&
.
cancel_running
(
retries:
1
)
...
@@ -20,10 +18,55 @@ class MergeTrain < ApplicationRecord
...
@@ -20,10 +18,55 @@ class MergeTrain < ApplicationRecord
end
end
end
end
enum
status:
%i[created merged stale fresh]
state_machine
:status
,
initial: :idle
do
event
:refresh_pipeline
do
transition
%i[idle stale fresh]
=>
:fresh
end
event
:outdate_pipeline
do
transition
fresh: :stale
end
event
:start_merge
do
transition
fresh: :merging
end
event
:finish_merge
do
transition
merging: :merged
end
before_transition
on: :refresh_pipeline
do
|
merge_train
,
transition
|
pipeline_id
=
transition
.
args
.
first
merge_train
.
pipeline_id
=
pipeline_id
end
before_transition
any
=>
:merged
do
|
merge_train
|
merged_at
=
Time
.
zone
.
now
merge_train
.
merged_at
=
merged_at
merge_train
.
duration
=
merged_at
-
merge_train
.
created_at
end
scope
:active
,
->
{
where
(
status:
active_statuses
)
}
after_transition
fresh: :stale
do
|
merge_train
|
scope
:merged
,
->
{
where
(
status:
merged_statuses
)
}
merge_train
.
run_after_commit
do
merge_train
.
refresh_async
end
end
after_transition
merging: :merged
do
|
merge_train
|
merge_train
.
run_after_commit
do
merge_train
.
cleanup_ref
end
end
state
:idle
,
value:
0
state
:merged
,
value:
1
state
:stale
,
value:
2
state
:fresh
,
value:
3
state
:merging
,
value:
4
end
scope
:active
,
->
{
with_status
(
*
ACTIVE_STATUSES
)
}
scope
:complete
,
->
{
with_status
(
*
COMPLETE_STATUSES
)
}
scope
:for_target
,
->
(
project_id
,
branch
)
{
where
(
target_project_id:
project_id
,
target_branch:
branch
)
}
scope
:for_target
,
->
(
project_id
,
branch
)
{
where
(
target_project_id:
project_id
,
target_branch:
branch
)
}
scope
:by_id
,
->
{
order
(
'merge_trains.id ASC'
)
}
scope
:by_id
,
->
{
order
(
'merge_trains.id ASC'
)
}
...
@@ -47,12 +90,12 @@ class MergeTrain < ApplicationRecord
...
@@ -47,12 +90,12 @@ class MergeTrain < ApplicationRecord
all_active_mrs_in_train
(
merge_request
.
target_project_id
,
merge_request
.
target_branch
).
where
(
id:
merge_request_ids
).
first
all_active_mrs_in_train
(
merge_request
.
target_project_id
,
merge_request
.
target_branch
).
where
(
id:
merge_request_ids
).
first
end
end
def
last_
merged
_mr_in_train
(
target_project_id
,
target_branch
)
def
last_
complete
_mr_in_train
(
target_project_id
,
target_branch
)
MergeRequest
.
where
(
id:
last_
merged
_merge_train
(
target_project_id
,
target_branch
)).
take
MergeRequest
.
where
(
id:
last_
complete
_merge_train
(
target_project_id
,
target_branch
)).
take
end
end
def
sha_exists_in_history?
(
target_project_id
,
target_branch
,
newrev
,
limit:
20
)
def
sha_exists_in_history?
(
target_project_id
,
target_branch
,
newrev
,
limit:
20
)
MergeRequest
.
exists?
(
id:
merged
_merge_trains
(
target_project_id
,
target_branch
,
limit:
limit
),
MergeRequest
.
exists?
(
id:
complete
_merge_trains
(
target_project_id
,
target_branch
,
limit:
limit
),
merge_commit_sha:
newrev
)
merge_commit_sha:
newrev
)
end
end
...
@@ -60,14 +103,6 @@ class MergeTrain < ApplicationRecord
...
@@ -60,14 +103,6 @@ class MergeTrain < ApplicationRecord
all_active_mrs_in_train
(
merge_request
.
target_project_id
,
merge_request
.
target_branch
).
count
all_active_mrs_in_train
(
merge_request
.
target_project_id
,
merge_request
.
target_branch
).
count
end
end
def
active_statuses
statuses
.
values_at
(
*
ACTIVE_STATUSES
)
end
def
merged_statuses
statuses
.
values_at
(
:merged
)
end
private
private
def
first_merge_request_ids
(
project
)
def
first_merge_request_ids
(
project
)
...
@@ -77,13 +112,13 @@ class MergeTrain < ApplicationRecord
...
@@ -77,13 +112,13 @@ class MergeTrain < ApplicationRecord
.
order
(
:target_branch
,
:id
)
.
order
(
:target_branch
,
:id
)
end
end
def
last_
merged
_merge_train
(
target_project_id
,
target_branch
)
def
last_
complete
_merge_train
(
target_project_id
,
target_branch
)
merged
_merge_trains
(
target_project_id
,
target_branch
,
limit:
1
)
complete
_merge_trains
(
target_project_id
,
target_branch
,
limit:
1
)
end
end
def
merged
_merge_trains
(
target_project_id
,
target_branch
,
limit
:)
def
complete
_merge_trains
(
target_project_id
,
target_branch
,
limit
:)
MergeTrain
.
for_target
(
target_project_id
,
target_branch
)
MergeTrain
.
for_target
(
target_project_id
,
target_branch
)
.
merged
.
order
(
id: :desc
).
select
(
:merge_request_id
).
limit
(
limit
)
.
complete
.
order
(
id: :desc
).
select
(
:merge_request_id
).
limit
(
limit
)
end
end
end
end
...
@@ -120,11 +155,9 @@ class MergeTrain < ApplicationRecord
...
@@ -120,11 +155,9 @@ class MergeTrain < ApplicationRecord
end
end
def
active?
def
active?
ACTIVE_STATUSES
.
include?
(
status
)
ACTIVE_STATUSES
.
include?
(
status
_name
.
to_s
)
end
end
private
def
refresh_async
def
refresh_async
AutoMergeProcessWorker
.
perform_async
(
merge_request_id
)
AutoMergeProcessWorker
.
perform_async
(
merge_request_id
)
end
end
...
...
ee/app/services/auto_merge/merge_train_service.rb
View file @
c1977621
...
@@ -26,7 +26,7 @@ module AutoMerge
...
@@ -26,7 +26,7 @@ module AutoMerge
super
do
super
do
if
merge_request
.
merge_train
&
.
destroy
if
merge_request
.
merge_train
&
.
destroy
SystemNoteService
.
cancel_merge_train
(
merge_request
,
project
,
current_user
)
SystemNoteService
.
cancel_merge_train
(
merge_request
,
project
,
current_user
)
next_merge_request
.
merge_train
.
stale!
if
next_merge_request
next_merge_request
.
merge_train
.
outdate_pipeline
if
next_merge_request
end
end
end
end
end
end
...
@@ -39,7 +39,7 @@ module AutoMerge
...
@@ -39,7 +39,7 @@ module AutoMerge
super
(
merge_request
,
reason
)
do
super
(
merge_request
,
reason
)
do
if
merge_request
.
merge_train
&
.
destroy
if
merge_request
.
merge_train
&
.
destroy
SystemNoteService
.
abort_merge_train
(
merge_request
,
project
,
current_user
,
reason
)
SystemNoteService
.
abort_merge_train
(
merge_request
,
project
,
current_user
,
reason
)
next_merge_request
.
merge_train
.
stale!
if
next_merge_request
&&
process_next
next_merge_request
.
merge_train
.
outdate_pipeline
if
next_merge_request
&&
process_next
end
end
end
end
end
end
...
...
ee/app/services/ee/merge_requests/refresh_service.rb
View file @
c1977621
...
@@ -48,6 +48,8 @@ module EE
...
@@ -48,6 +48,8 @@ module EE
# rubocop:disable Gitlab/ModuleWithInstanceVariables
# rubocop:disable Gitlab/ModuleWithInstanceVariables
def
check_merge_train_status
def
check_merge_train_status
return
unless
@push
.
branch_updated?
MergeTrains
::
CheckStatusService
.
new
(
project
,
current_user
)
MergeTrains
::
CheckStatusService
.
new
(
project
,
current_user
)
.
execute
(
project
,
@push
.
branch_name
,
@push
.
newrev
)
.
execute
(
project
,
@push
.
branch_name
,
@push
.
newrev
)
end
end
...
...
ee/app/services/merge_trains/check_status_service.rb
View file @
c1977621
...
@@ -9,7 +9,7 @@ module MergeTrains
...
@@ -9,7 +9,7 @@ module MergeTrains
# that means there was an unexpected commit came from out of merge train cycle.
# that means there was an unexpected commit came from out of merge train cycle.
unless
MergeTrain
.
sha_exists_in_history?
(
target_project
.
id
,
target_branch
,
newrev
)
unless
MergeTrain
.
sha_exists_in_history?
(
target_project
.
id
,
target_branch
,
newrev
)
merge_request
=
MergeTrain
.
first_in_train
(
target_project
.
id
,
target_branch
)
merge_request
=
MergeTrain
.
first_in_train
(
target_project
.
id
,
target_branch
)
merge_request
.
merge_train
.
stale!
if
merge_request
merge_request
.
merge_train
.
outdate_pipeline
if
merge_request
end
end
end
end
end
end
...
...
ee/app/services/merge_trains/refresh_merge_request_service.rb
View file @
c1977621
...
@@ -33,10 +33,6 @@ module MergeTrains
...
@@ -33,10 +33,6 @@ module MergeTrains
raise
ProcessError
,
'merge request is not on a merge train'
raise
ProcessError
,
'merge request is not on a merge train'
end
end
if
merge_request
.
squash?
&&
Feature
.
disabled?
(
:merge_train_new_stale_check
,
project
,
default_enabled:
true
)
raise
ProcessError
,
'merge train does not support squash merge'
end
unless
merge_request
.
mergeable_state?
(
skip_ci_check:
true
)
unless
merge_request
.
mergeable_state?
(
skip_ci_check:
true
)
raise
ProcessError
,
'merge request is not mergeable'
raise
ProcessError
,
'merge request is not mergeable'
end
end
...
@@ -52,9 +48,6 @@ module MergeTrains
...
@@ -52,9 +48,6 @@ module MergeTrains
end
end
end
end
# Since `stale_pipeline?` is expensive process which requires multiple Gitaly calls,
# each refresh service relays `require_recreate` flag whether the next
# merge request obviously requires to re-create pipeline for merge train.
def
should_create_pipeline?
def
should_create_pipeline?
pipeline_absent?
||
require_recreate?
||
stale_pipeline?
pipeline_absent?
||
require_recreate?
||
stale_pipeline?
end
end
...
@@ -70,41 +63,22 @@ module MergeTrains
...
@@ -70,41 +63,22 @@ module MergeTrains
end
end
def
should_merge?
def
should_merge?
first_in_train?
&&
pipeline_for_merge_train
&
.
success
?
pipeline_for_merge_train
&
.
success?
&&
first_in_train
?
end
end
def
merge!
def
merge!
merge_train
.
start_merge!
MergeRequests
::
MergeService
.
new
(
project
,
merge_user
,
merge_request
.
merge_params
)
MergeRequests
::
MergeService
.
new
(
project
,
merge_user
,
merge_request
.
merge_params
)
.
execute
(
merge_request
)
.
execute
(
merge_request
)
raise
ProcessError
,
"failed to merge.
#{
merge_request
.
merge_error
}
"
unless
merge_request
.
merged?
raise
ProcessError
,
"failed to merge.
#{
merge_request
.
merge_error
}
"
unless
merge_request
.
merged?
end
def
stale_pipeline?
merge_train
.
finish_merge!
if
Feature
.
enabled?
(
:merge_train_new_stale_check
,
project
,
default_enabled:
true
)
merge_train
.
stale?
else
legacy_stale_pipeline?
end
end
end
# NOTE: This method works for both no-ff-merge and ff-merge, however,
def
stale_pipeline?
# it doesn't work for squash and merge option.
merge_train
.
stale?
def
legacy_stale_pipeline?
return
true
unless
pipeline_for_merge_train
.
source_sha
==
merge_request
.
diff_head_sha
return
false
if
pipeline_for_merge_train
.
target_sha
==
previous_ref_sha
##
# Now `pipeline.target_sha` and `previous_ref_sha` are different. This case
# happens in the following cases:
# 1. Previous sha has a completely different history from the pipeline.target_sha.
# e.g. Previous merge request was dropped from the merge train.
# 2. Previous sha has exactly the same history with the pipeline.target_sha.
# e.g. Previous merge request was merged into target branch with no-ff option.
#
# We distinguish these two cases by comparing parent commits.
commits
=
merge_request
.
project
.
commits_by
(
oids:
[
pipeline_for_merge_train
.
target_sha
,
previous_ref_sha
])
commits
[
0
].
parent_ids
!=
commits
[
1
].
parent_ids
end
end
def
pipeline_absent?
def
pipeline_absent?
...
@@ -128,8 +102,7 @@ module MergeTrains
...
@@ -128,8 +102,7 @@ module MergeTrains
end
end
def
update_pipeline_for_merge_train
(
pipeline
)
def
update_pipeline_for_merge_train
(
pipeline
)
merge_train
.
pipeline
=
pipeline
merge_train
.
refresh_pipeline!
(
pipeline
.
id
)
merge_train
.
fresh!
end
end
def
merge_user
def
merge_user
...
...
ee/app/services/merge_trains/refresh_merge_requests_service.rb
View file @
c1977621
...
@@ -15,16 +15,6 @@ module MergeTrains
...
@@ -15,16 +15,6 @@ module MergeTrains
def
execute
(
merge_request
)
def
execute
(
merge_request
)
return
unless
merge_request
.
on_train?
return
unless
merge_request
.
on_train?
if
Feature
.
enabled?
(
:merge_trains_efficient_refresh
,
default_enabled:
true
)
efficient_refresh
(
merge_request
)
else
legacy_refresh
(
merge_request
)
end
end
private
def
efficient_refresh
(
merge_request
)
queue
=
Gitlab
::
BatchPopQueueing
.
new
(
'merge_trains'
,
queue_id
(
merge_request
))
queue
=
Gitlab
::
BatchPopQueueing
.
new
(
'merge_trains'
,
queue_id
(
merge_request
))
result
=
queue
.
safe_execute
([
merge_request
.
id
],
lock_timeout:
15
.
minutes
)
do
|
items
|
result
=
queue
.
safe_execute
([
merge_request
.
id
],
lock_timeout:
15
.
minutes
)
do
|
items
|
...
@@ -39,11 +29,7 @@ module MergeTrains
...
@@ -39,11 +29,7 @@ module MergeTrains
end
end
end
end
def
legacy_refresh
(
merge_request
)
private
in_lock
(
"merge_train:
#{
merge_request
.
target_project_id
}
-
#{
merge_request
.
target_branch
}
"
)
do
unsafe_refresh
(
merge_request
)
end
end
def
unsafe_refresh
(
merge_request
)
def
unsafe_refresh
(
merge_request
)
require_next_recreate
=
false
require_next_recreate
=
false
...
...
ee/spec/factories/merge_requests.rb
View file @
c1977621
...
@@ -11,7 +11,7 @@ FactoryBot.modify do
...
@@ -11,7 +11,7 @@ FactoryBot.modify do
trait
:on_train
do
trait
:on_train
do
transient
do
transient
do
train_creator
{
author
}
train_creator
{
author
}
status
{
'
created
'
}
status
{
'
idle
'
}
end
end
auto_merge_enabled
{
true
}
auto_merge_enabled
{
true
}
...
...
ee/spec/factories/merge_trains.rb
View file @
c1977621
...
@@ -8,20 +8,24 @@ FactoryBot.define do
...
@@ -8,20 +8,24 @@ FactoryBot.define do
user
user
pipeline
factory: :ci_pipeline
pipeline
factory: :ci_pipeline
trait
:
created
do
trait
:
idle
do
status
{
:created
}
status
{
MergeTrain
.
state_machines
[
:status
].
states
[
:idle
].
value
}
end
end
trait
:merged
do
trait
:merged
do
status
{
:merged
}
status
{
MergeTrain
.
state_machines
[
:status
].
states
[
:merged
].
value
}
end
trait
:merging
do
status
{
MergeTrain
.
state_machines
[
:status
].
states
[
:merging
].
value
}
end
end
trait
:stale
do
trait
:stale
do
status
{
:stal
e
}
status
{
MergeTrain
.
state_machines
[
:status
].
states
[
:stale
].
valu
e
}
end
end
trait
:fresh
do
trait
:fresh
do
status
{
:fresh
}
status
{
MergeTrain
.
state_machines
[
:status
].
states
[
:fresh
].
value
}
end
end
end
end
end
end
ee/spec/features/merge_trains/two_merge_requests_on_train_spec.rb
View file @
c1977621
...
@@ -238,7 +238,7 @@ describe 'Two merge requests on a merge train' do
...
@@ -238,7 +238,7 @@ describe 'Two merge requests on a merge train' do
end
end
it
'does not recreate pipeline when merge request 1 refreshed again'
do
it
'does not recreate pipeline when merge request 1 refreshed again'
do
expect
{
merge_request_1
.
merge_train
.
send
(
:refresh_async
)
}
expect
{
AutoMergeProcessWorker
.
perform_async
(
merge_request_1
.
id
)
}
.
not_to
change
{
merge_request_1
.
all_pipelines
.
count
}
.
not_to
change
{
merge_request_1
.
all_pipelines
.
count
}
end
end
...
...
ee/spec/models/merge_request_spec.rb
View file @
c1977621
...
@@ -748,7 +748,9 @@ describe MergeRequest do
...
@@ -748,7 +748,9 @@ describe MergeRequest do
context
'when the merge request was on a merge train'
do
context
'when the merge request was on a merge train'
do
let
(
:merge_request
)
do
let
(
:merge_request
)
do
create
(
:merge_request
,
:on_train
,
status:
'merged'
,
source_project:
project
,
target_project:
project
)
create
(
:merge_request
,
:on_train
,
status:
MergeTrain
.
state_machines
[
:status
].
states
[
:merged
].
value
,
source_project:
project
,
target_project:
project
)
end
end
it
{
is_expected
.
to
be_falsy
}
it
{
is_expected
.
to
be_falsy
}
...
@@ -763,34 +765,6 @@ describe MergeRequest do
...
@@ -763,34 +765,6 @@ describe MergeRequest do
end
end
end
end
describe
'state machine'
do
context
'when the merge request is on a merge train'
do
let
(
:merge_request
)
do
create
(
:merge_request
,
:on_train
,
source_project:
project
,
target_project:
project
)
end
context
'when the merge request is merged'
do
it
'ensures to finish merge train'
do
expect
(
merge_request
.
merge_train
).
to
receive
(
:merged!
)
merge_request
.
mark_as_merged!
end
end
end
context
'when the merge request is not on a merge train'
do
let
(
:merge_request
)
do
create
(
:merge_request
,
source_project:
project
,
target_project:
project
)
end
context
'when the merge request is merged'
do
it
'does not raise error'
do
expect
{
merge_request
.
mark_as_merged!
}.
not_to
raise_error
end
end
end
end
describe
'review time sorting'
do
describe
'review time sorting'
do
it
'orders by first_comment_at'
do
it
'orders by first_comment_at'
do
merge_request_1
=
create
(
:merge_request
,
:with_productivity_metrics
,
metrics_data:
{
first_comment_at:
1
.
day
.
ago
})
merge_request_1
=
create
(
:merge_request
,
:with_productivity_metrics
,
metrics_data:
{
first_comment_at:
1
.
day
.
ago
})
...
...
ee/spec/models/merge_train_spec.rb
View file @
c1977621
This diff is collapsed.
Click to expand it.
ee/spec/services/auto_merge/merge_train_service_spec.rb
View file @
c1977621
...
@@ -160,9 +160,12 @@ describe AutoMerge::MergeTrainService do
...
@@ -160,9 +160,12 @@ describe AutoMerge::MergeTrainService do
let!
(
:merge_request_2
)
do
let!
(
:merge_request_2
)
do
create
(
:merge_request
,
:on_train
,
create
(
:merge_request
,
:on_train
,
source_project:
project
,
source_branch:
'signed-commits'
,
source_project:
project
,
source_branch:
'signed-commits'
,
target_project:
project
,
target_branch:
'master'
)
target_project:
project
,
target_branch:
'master'
,
status:
status
)
end
end
let
(
:status
)
{
MergeTrain
.
state_machines
[
:status
].
states
[
:fresh
].
value
}
it
'processes the next merge request on the train by default'
do
it
'processes the next merge request on the train by default'
do
expect
(
AutoMergeProcessWorker
).
to
receive
(
:perform_async
).
with
(
merge_request_2
.
id
)
expect
(
AutoMergeProcessWorker
).
to
receive
(
:perform_async
).
with
(
merge_request_2
.
id
)
...
@@ -170,6 +173,18 @@ describe AutoMerge::MergeTrainService do
...
@@ -170,6 +173,18 @@ describe AutoMerge::MergeTrainService do
expect
(
merge_request_2
.
reset
.
merge_train
).
to
be_stale
expect
(
merge_request_2
.
reset
.
merge_train
).
to
be_stale
end
end
context
'when the status is stale already'
do
let
(
:status
)
{
MergeTrain
.
state_machines
[
:status
].
states
[
:stale
].
value
}
it
'does not do anything'
do
expect
(
AutoMergeProcessWorker
).
not_to
receive
(
:perform_async
).
with
(
merge_request_2
.
id
)
expect
{
subject
}.
not_to
raise_error
expect
(
merge_request_2
.
reset
.
merge_train
).
to
be_stale
end
end
end
end
end
end
...
@@ -208,7 +223,8 @@ describe AutoMerge::MergeTrainService do
...
@@ -208,7 +223,8 @@ describe AutoMerge::MergeTrainService do
let!
(
:merge_request_2
)
do
let!
(
:merge_request_2
)
do
create
(
:merge_request
,
:on_train
,
create
(
:merge_request
,
:on_train
,
source_project:
project
,
source_branch:
'signed-commits'
,
source_project:
project
,
source_branch:
'signed-commits'
,
target_project:
project
,
target_branch:
'master'
)
target_project:
project
,
target_branch:
'master'
,
status:
MergeTrain
.
state_machines
[
:status
].
states
[
:fresh
].
value
)
end
end
it
'processes the next merge request on the train'
do
it
'processes the next merge request on the train'
do
...
...
ee/spec/services/ee/merge_requests/refresh_service_spec.rb
View file @
c1977621
...
@@ -50,6 +50,16 @@ describe MergeRequests::RefreshService do
...
@@ -50,6 +50,16 @@ describe MergeRequests::RefreshService do
subject
subject
end
end
context
'when branch is deleted'
do
let
(
:newrev
)
{
Gitlab
::
Git
::
BLANK_SHA
}
it
'does not check merge train status'
do
expect
(
MergeTrains
::
CheckStatusService
).
not_to
receive
(
:new
)
subject
end
end
context
'#update_approvers'
do
context
'#update_approvers'
do
let
(
:owner
)
{
create
(
:user
)
}
let
(
:owner
)
{
create
(
:user
)
}
let
(
:current_user
)
{
merge_request
.
author
}
let
(
:current_user
)
{
merge_request
.
author
}
...
...
ee/spec/services/merge_trains/check_status_service_spec.rb
View file @
c1977621
...
@@ -25,7 +25,7 @@ describe MergeTrains::CheckStatusService do
...
@@ -25,7 +25,7 @@ describe MergeTrains::CheckStatusService do
create
(
:merge_request
,
:on_train
,
train_creator:
maintainer
,
create
(
:merge_request
,
:on_train
,
train_creator:
maintainer
,
source_branch:
'feature'
,
source_project:
project
,
source_branch:
'feature'
,
source_project:
project
,
target_branch:
'master'
,
target_project:
project
,
target_branch:
'master'
,
target_project:
project
,
merge_status:
'unchecked'
)
merge_status:
'unchecked'
,
status:
MergeTrain
.
state_machines
[
:status
].
states
[
:merged
].
value
)
end
end
let!
(
:active_merge_request
)
do
let!
(
:active_merge_request
)
do
...
@@ -43,9 +43,9 @@ describe MergeTrains::CheckStatusService do
...
@@ -43,9 +43,9 @@ describe MergeTrains::CheckStatusService do
context
'when new revision is included in merge train history'
do
context
'when new revision is included in merge train history'
do
let!
(
:merge_commit_sha_1
)
{
Digest
::
SHA1
.
hexdigest
'test'
}
let!
(
:merge_commit_sha_1
)
{
Digest
::
SHA1
.
hexdigest
'test'
}
it
'does not
mark merge train as stal
e'
do
it
'does not
outdate the merge train pipelin
e'
do
expect
(
MergeTrain
).
to
receive
(
:sha_exists_in_history?
).
and_return
(
true
).
and_call_original
expect
(
MergeTrain
).
to
receive
(
:sha_exists_in_history?
).
and_return
(
true
).
and_call_original
expect_any_instance_of
(
MergeTrain
).
not_to
receive
(
:
stale!
)
expect_any_instance_of
(
MergeTrain
).
not_to
receive
(
:
outdate_pipeline
)
subject
subject
end
end
...
@@ -54,9 +54,9 @@ describe MergeTrains::CheckStatusService do
...
@@ -54,9 +54,9 @@ describe MergeTrains::CheckStatusService do
context
'when new revision is not included in merge train history'
do
context
'when new revision is not included in merge train history'
do
let!
(
:merge_commit_sha_1
)
{
Digest
::
SHA1
.
hexdigest
'other'
}
let!
(
:merge_commit_sha_1
)
{
Digest
::
SHA1
.
hexdigest
'other'
}
it
'
marks the merge train as stal
e'
do
it
'
outdates the merge train pipelin
e'
do
expect
(
MergeTrain
).
to
receive
(
:sha_exists_in_history?
).
and_return
(
false
).
and_call_original
expect
(
MergeTrain
).
to
receive
(
:sha_exists_in_history?
).
and_return
(
false
).
and_call_original
expect_any_instance_of
(
MergeTrain
).
to
receive
(
:
stale!
)
expect_any_instance_of
(
MergeTrain
).
to
receive
(
:
outdate_pipeline
)
subject
subject
end
end
...
...
ee/spec/services/merge_trains/refresh_merge_request_service_spec.rb
View file @
c1977621
...
@@ -122,21 +122,9 @@ describe MergeTrains::RefreshMergeRequestService do
...
@@ -122,21 +122,9 @@ describe MergeTrains::RefreshMergeRequestService do
merge_request
.
update!
(
squash:
true
)
merge_request
.
update!
(
squash:
true
)
end
end
context
'when merge_train_new_stale_check feature flag is enabled'
do
let
(
:pipeline
)
{
create
(
:ci_pipeline
)
}
let
(
:pipeline
)
{
create
(
:ci_pipeline
)
}
it_behaves_like
'creates a pipeline for merge train'
end
context
'when merge_train_new_stale_check feature flag is disabled'
do
before
do
stub_feature_flags
(
merge_train_new_stale_check:
false
)
end
it_behaves_like
'drops the merge request from the merge train'
do
it_behaves_like
'creates a pipeline for merge train'
let
(
:expected_reason
)
{
'merge train does not support squash merge'
}
end
end
end
end
context
'when previous ref is not found'
do
context
'when previous ref is not found'
do
...
@@ -174,7 +162,7 @@ describe MergeTrains::RefreshMergeRequestService do
...
@@ -174,7 +162,7 @@ describe MergeTrains::RefreshMergeRequestService do
let
(
:previous_ref_sha
)
{
project
.
repository
.
commit
(
'refs/heads/master'
).
sha
}
let
(
:previous_ref_sha
)
{
project
.
repository
.
commit
(
'refs/heads/master'
).
sha
}
before
do
before
do
merge_request
.
merge_train
.
update!
(
pipeline:
pipeline
)
merge_request
.
merge_train
.
refresh_pipeline!
(
pipeline
.
id
)
end
end
context
'when the pipeline is not stale'
do
context
'when the pipeline is not stale'
do
...
@@ -182,23 +170,11 @@ describe MergeTrains::RefreshMergeRequestService do
...
@@ -182,23 +170,11 @@ describe MergeTrains::RefreshMergeRequestService do
end
end
context
'when the pipeline is stale'
do
context
'when the pipeline is stale'
do
context
'when merge_train_new_stale_check feature flag is enabled'
do
before
do
before
do
merge_request
.
merge_train
.
update_column
(
:status
,
MergeTrain
.
state_machines
[
:status
].
states
[
:stale
].
value
)
merge_request
.
merge_train
.
update_column
(
:status
,
:stale
)
end
it_behaves_like
'cancels and recreates a pipeline for the merge train'
end
end
context
'when merge_train_new_stale_check feature flag is disabled'
do
it_behaves_like
'cancels and recreates a pipeline for the merge train'
before
do
stub_feature_flags
(
merge_train_new_stale_check:
false
)
end
let
(
:previous_ref_sha
)
{
project
.
repository
.
commits
(
'refs/heads/master'
,
limit:
2
).
last
.
sha
}
it_behaves_like
'cancels and recreates a pipeline for the merge train'
end
end
end
context
'when the pipeline is required to be recreated'
do
context
'when the pipeline is required to be recreated'
do
...
@@ -213,7 +189,7 @@ describe MergeTrains::RefreshMergeRequestService do
...
@@ -213,7 +189,7 @@ describe MergeTrains::RefreshMergeRequestService do
let
(
:previous_ref_sha
)
{
project
.
repository
.
commit
(
'refs/heads/master'
).
sha
}
let
(
:previous_ref_sha
)
{
project
.
repository
.
commit
(
'refs/heads/master'
).
sha
}
before
do
before
do
merge_request
.
merge_train
.
pipeline
=
pipeline
merge_request
.
merge_train
.
refresh_pipeline!
(
pipeline
.
id
)
merge_request
.
merge_params
[
:sha
]
=
merge_request
.
diff_head_sha
merge_request
.
merge_params
[
:sha
]
=
merge_request
.
diff_head_sha
merge_request
.
save
merge_request
.
save
end
end
...
@@ -221,12 +197,14 @@ describe MergeTrains::RefreshMergeRequestService do
...
@@ -221,12 +197,14 @@ describe MergeTrains::RefreshMergeRequestService do
context
'when the merge request is the first queue'
do
context
'when the merge request is the first queue'
do
it
'merges the merge request'
do
it
'merges the merge request'
do
expect
(
merge_request
).
to
receive
(
:cleanup_refs
).
with
(
only: :train
)
expect
(
merge_request
).
to
receive
(
:cleanup_refs
).
with
(
only: :train
)
expect
(
merge_request
.
merge_train
).
to
receive
(
:start_merge!
).
and_call_original
expect
(
merge_request
.
merge_train
).
to
receive
(
:finish_merge!
).
and_call_original
expect_next_instance_of
(
MergeRequests
::
MergeService
,
project
,
maintainer
,
anything
)
do
|
service
|
expect_next_instance_of
(
MergeRequests
::
MergeService
,
project
,
maintainer
,
anything
)
do
|
service
|
expect
(
service
).
to
receive
(
:execute
).
with
(
merge_request
).
and_call_original
expect
(
service
).
to
receive
(
:execute
).
with
(
merge_request
).
and_call_original
end
end
expect
{
subject
}
expect
{
subject
}
.
to
change
{
merge_request
.
merge_train
.
status
}.
from
(
'created'
).
to
(
'merged'
)
.
to
change
{
merge_request
.
merge_train
.
status
_name
}.
from
(
:fresh
).
to
(
:merged
)
end
end
context
'when it failed to merge the merge request'
do
context
'when it failed to merge the merge request'
do
...
@@ -236,6 +214,16 @@ describe MergeTrains::RefreshMergeRequestService do
...
@@ -236,6 +214,16 @@ describe MergeTrains::RefreshMergeRequestService do
allow_any_instance_of
(
MergeRequests
::
MergeService
).
to
receive
(
:execute
)
{
{
result: :error
}
}
allow_any_instance_of
(
MergeRequests
::
MergeService
).
to
receive
(
:execute
)
{
{
result: :error
}
}
end
end
it
'does not finish merge and drops the merge request from train'
do
expect
(
merge_request
).
to
be_on_train
expect
(
merge_request
.
merge_train
).
to
receive
(
:start_merge!
).
and_call_original
expect
(
merge_request
.
merge_train
).
not_to
receive
(
:finish_merge!
)
subject
expect
(
merge_request
).
not_to
be_on_train
end
it_behaves_like
'drops the merge request from the merge train'
do
it_behaves_like
'drops the merge request from the merge train'
do
let
(
:expected_reason
)
{
'failed to merge. Branch has been updated since the merge was requested.'
}
let
(
:expected_reason
)
{
'failed to merge. Branch has been updated since the merge was requested.'
}
end
end
...
...
ee/spec/services/merge_trains/refresh_merge_requests_service_spec.rb
View file @
c1977621
...
@@ -115,7 +115,7 @@ describe MergeTrains::RefreshMergeRequestsService do
...
@@ -115,7 +115,7 @@ describe MergeTrains::RefreshMergeRequestsService do
context
'when merge request 1 was on a merge train'
do
context
'when merge request 1 was on a merge train'
do
before
do
before
do
allow
(
merge_request_1
.
merge_train
).
to
receive
(
:cleanup_ref
)
allow
(
merge_request_1
.
merge_train
).
to
receive
(
:cleanup_ref
)
merge_request_1
.
merge_train
.
merged!
merge_request_1
.
merge_train
.
update_column
(
:status
,
MergeTrain
.
state_machines
[
:status
].
states
[
:merged
].
value
)
end
end
it
'does not refresh'
do
it
'does not refresh'
do
...
@@ -146,26 +146,6 @@ describe MergeTrains::RefreshMergeRequestsService do
...
@@ -146,26 +146,6 @@ describe MergeTrains::RefreshMergeRequestsService do
subject
subject
end
end
end
end
context
'when merge_trains_efficient_refresh is disabled'
do
before
do
stub_feature_flags
(
merge_trains_efficient_refresh:
false
)
end
context
'when the exclusive 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
end
end
context
'when merge request 2 is passed'
do
context
'when merge request 2 is passed'
do
...
@@ -194,7 +174,7 @@ describe MergeTrains::RefreshMergeRequestsService do
...
@@ -194,7 +174,7 @@ describe MergeTrains::RefreshMergeRequestsService do
context
'when merge request 1 has already been merged'
do
context
'when merge request 1 has already been merged'
do
before
do
before
do
allow
(
merge_request_1
.
merge_train
).
to
receive
(
:cleanup_ref
)
allow
(
merge_request_1
.
merge_train
).
to
receive
(
:cleanup_ref
)
merge_request_1
.
merge_train
.
merged!
merge_request_1
.
merge_train
.
update_column
(
:status
,
MergeTrain
.
state_machines
[
:status
].
states
[
:merged
].
value
)
end
end
it
'does not refresh the merge request 1'
do
it
'does not refresh the merge request 1'
do
...
...
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