Commit 0d9b801a authored by Shinya Maeda's avatar Shinya Maeda

Refactor the logic of updating head pipelines

Sort out some logic
parent c50b0e58
...@@ -178,6 +178,14 @@ module Ci ...@@ -178,6 +178,14 @@ module Ci
scope :for_user, -> (user) { where(user: user) } scope :for_user, -> (user) { where(user: user) }
scope :for_merge_request, -> (merge_request, ref, sha) do
##
# We have to filter out unrelated MR pipelines, in case,
# there are two merge requests from the same source branch
where(merge_request: [nil, merge_request], ref: ref, sha: sha)
.sort_by_merge_request_pipelines
end
# Returns the pipelines in descending order (= newest first), optionally # Returns the pipelines in descending order (= newest first), optionally
# limited to a number of references. # limited to a number of references.
# #
...@@ -265,6 +273,10 @@ module Ci ...@@ -265,6 +273,10 @@ module Ci
sources.reject { |source| source == "external" }.values sources.reject { |source| source == "external" }.values
end end
def self.latest_for_merge_request(merge_request, ref, sha)
for_merge_request(merge_request, ref, sha).first
end
def self.ci_sources_values def self.ci_sources_values
config_sources.values_at(:repository_source, :auto_devops_source, :unknown_source) config_sources.values_at(:repository_source, :auto_devops_source, :unknown_source)
end end
......
...@@ -1092,10 +1092,15 @@ class MergeRequest < ActiveRecord::Base ...@@ -1092,10 +1092,15 @@ class MergeRequest < ActiveRecord::Base
def all_pipelines(shas: all_commit_shas) def all_pipelines(shas: all_commit_shas)
return Ci::Pipeline.none unless source_project return Ci::Pipeline.none unless source_project
@all_pipelines ||= source_project.ci_pipelines @all_pipelines ||=
.where(sha: shas, ref: source_branch) source_project.ci_pipelines
.where(merge_request: [nil, self]) .for_merge_request(self, source_branch, all_commit_shas)
.sort_by_merge_request_pipelines end
def update_head_pipeline
self.head_pipeline = find_actual_head_pipeline
update_column(:head_pipeline_id, head_pipeline.id) if head_pipeline_id_changed?
end end
def merge_request_pipeline_exists? def merge_request_pipeline_exists?
...@@ -1295,6 +1300,11 @@ class MergeRequest < ActiveRecord::Base ...@@ -1295,6 +1300,11 @@ class MergeRequest < ActiveRecord::Base
.find_by(sha: diff_base_sha) .find_by(sha: diff_base_sha)
end end
def find_actual_head_pipeline
source_project&.ci_pipelines
&.latest_for_merge_request(self, source_branch, diff_head_sha)
end
def discussions_rendered_on_frontend? def discussions_rendered_on_frontend?
true true
end end
......
...@@ -26,7 +26,7 @@ module MergeRequests ...@@ -26,7 +26,7 @@ module MergeRequests
todo_service.new_merge_request(issuable, current_user) todo_service.new_merge_request(issuable, current_user)
issuable.cache_merge_request_closes_issues!(current_user) issuable.cache_merge_request_closes_issues!(current_user)
create_merge_request_pipeline(issuable, current_user) create_merge_request_pipeline(issuable, current_user)
update_merge_requests_head_pipeline(issuable) issuable.update_head_pipeline
super super
end end
...@@ -45,20 +45,6 @@ module MergeRequests ...@@ -45,20 +45,6 @@ module MergeRequests
private private
def update_merge_requests_head_pipeline(merge_request)
pipeline = head_pipeline_for(merge_request)
merge_request.update(head_pipeline_id: pipeline.id) if pipeline
end
def head_pipeline_for(merge_request)
return unless merge_request.source_project
sha = merge_request.source_branch_sha
return unless sha
merge_request.all_pipelines(shas: sha).first
end
def set_projects! def set_projects!
# @project is used to determine whether the user can set the merge request's # @project is used to determine whether the user can set the merge request's
# assignee, milestone and labels. Whether they can depends on their # assignee, milestone and labels. Whether they can depends on their
......
...@@ -7,25 +7,8 @@ class UpdateHeadPipelineForMergeRequestWorker ...@@ -7,25 +7,8 @@ class UpdateHeadPipelineForMergeRequestWorker
queue_namespace :pipeline_processing queue_namespace :pipeline_processing
def perform(merge_request_id) def perform(merge_request_id)
merge_request = MergeRequest.find(merge_request_id) MergeRequest.find_by_id(merge_request_id).try do |merge_request|
merge_request.update_head_pipeline
sha = merge_request.diff_head_sha
pipeline = merge_request.all_pipelines(shas: sha).first
return unless pipeline && pipeline.latest?
if merge_request.diff_head_sha != pipeline.sha
log_error_message_for(merge_request)
return
end end
merge_request.update_attribute(:head_pipeline_id, pipeline.id)
end
def log_error_message_for(merge_request)
Rails.logger.error(
"Outdated head pipeline for active merge request: id=#{merge_request.id}, source_branch=#{merge_request.source_branch}, diff_head_sha=#{merge_request.diff_head_sha}"
)
end end
end end
---
title: Refactor the logic of updating head pipelines for merge requests
merge_request: 23437
author:
type: other
...@@ -1372,6 +1372,34 @@ describe MergeRequest do ...@@ -1372,6 +1372,34 @@ describe MergeRequest do
end end
end end
describe '#update_head_pipeline' do
subject { merge_request.update_head_pipeline }
let(:merge_request) { create(:merge_request) }
context 'when there is a pipeline with the diff head sha' do
let!(:pipeline) do
create(:ci_empty_pipeline,
project: merge_request.project,
sha: merge_request.diff_head_sha,
ref: merge_request.source_branch)
end
it 'updates the head pipeline' do
expect { subject }
.to change { merge_request.reload.head_pipeline }
.from(nil).to(pipeline)
end
end
context 'when there are no pipelines with the diff head sha' do
it 'does not update the head pipeline' do
expect { subject }
.not_to change { merge_request.reload.head_pipeline }
end
end
end
describe '#has_test_reports?' do describe '#has_test_reports?' do
subject { merge_request.has_test_reports? } subject { merge_request.has_test_reports? }
......
...@@ -143,7 +143,8 @@ describe Ci::CreatePipelineService do ...@@ -143,7 +143,8 @@ describe Ci::CreatePipelineService do
target_branch: "branch_1", target_branch: "branch_1",
source_project: project) source_project: project)
allow_any_instance_of(Ci::Pipeline).to receive(:latest?).and_return(false) allow_any_instance_of(MergeRequest)
.to receive(:find_actual_head_pipeline) { }
execute_service execute_service
......
...@@ -128,9 +128,9 @@ describe MergeRequests::CreateService do ...@@ -128,9 +128,9 @@ describe MergeRequests::CreateService do
end end
context 'when head pipelines already exist for merge request source branch' do context 'when head pipelines already exist for merge request source branch' do
let(:sha) { project.commit(opts[:source_branch]).id } let(:shas) { project.repository.commits(opts[:source_branch], limit: 2).map(&:id) }
let!(:pipeline_1) { create(:ci_pipeline, project: project, ref: opts[:source_branch], project_id: project.id, sha: sha) } let!(:pipeline_1) { create(:ci_pipeline, project: project, ref: opts[:source_branch], project_id: project.id, sha: shas[1]) }
let!(:pipeline_2) { create(:ci_pipeline, project: project, ref: opts[:source_branch], project_id: project.id, sha: sha) } let!(:pipeline_2) { create(:ci_pipeline, project: project, ref: opts[:source_branch], project_id: project.id, sha: shas[0]) }
let!(:pipeline_3) { create(:ci_pipeline, project: project, ref: "other_branch", project_id: project.id) } let!(:pipeline_3) { create(:ci_pipeline, project: project, ref: "other_branch", project_id: project.id) }
before do before do
...@@ -144,17 +144,30 @@ describe MergeRequests::CreateService do ...@@ -144,17 +144,30 @@ describe MergeRequests::CreateService do
it 'sets head pipeline' do it 'sets head pipeline' do
merge_request = service.execute merge_request = service.execute
expect(merge_request.head_pipeline).to eq(pipeline_2) expect(merge_request.reload.head_pipeline).to eq(pipeline_2)
expect(merge_request).to be_persisted expect(merge_request).to be_persisted
end end
context 'when merge request head commit sha does not match pipeline sha' do context 'when the new pipeline is associated with an old sha' do
it 'sets the head pipeline correctly' do let!(:pipeline_1) { create(:ci_pipeline, project: project, ref: opts[:source_branch], project_id: project.id, sha: shas[0]) }
pipeline_2.update(sha: 1234) let!(:pipeline_2) { create(:ci_pipeline, project: project, ref: opts[:source_branch], project_id: project.id, sha: shas[1]) }
it 'sets an old pipeline with associated with the latest sha as the head pipeline' do
merge_request = service.execute merge_request = service.execute
expect(merge_request.head_pipeline).to eq(pipeline_1) expect(merge_request.reload.head_pipeline).to eq(pipeline_1)
expect(merge_request).to be_persisted
end
end
context 'when there are no pipelines with the diff head sha' do
let!(:pipeline_1) { create(:ci_pipeline, project: project, ref: opts[:source_branch], project_id: project.id, sha: shas[1]) }
let!(:pipeline_2) { create(:ci_pipeline, project: project, ref: opts[:source_branch], project_id: project.id, sha: shas[1]) }
it 'does not set the head pipeline' do
merge_request = service.execute
expect(merge_request.reload.head_pipeline).to be_nil
expect(merge_request).to be_persisted expect(merge_request).to be_persisted
end end
end end
......
...@@ -21,17 +21,17 @@ describe UpdateHeadPipelineForMergeRequestWorker do ...@@ -21,17 +21,17 @@ describe UpdateHeadPipelineForMergeRequestWorker do
merge_request.merge_request_diff.update(head_commit_sha: 'different_sha') merge_request.merge_request_diff.update(head_commit_sha: 'different_sha')
end end
it 'does not update head_pipeline_id' do it 'does not update head pipeline' do
expect { subject.perform(merge_request.id) }.not_to raise_error expect { subject.perform(merge_request.id) }
.not_to change { merge_request.reload.head_pipeline_id }
expect(merge_request.reload.head_pipeline_id).to eq(nil)
end end
end end
end end
context 'when pipeline does not exist for the source project and branch' do context 'when pipeline does not exist for the source project and branch' do
it 'does not update the head_pipeline_id of the merge_request' do it 'does not update the head_pipeline_id of the merge_request' do
expect { subject.perform(merge_request.id) }.not_to change { merge_request.reload.head_pipeline_id } expect { subject.perform(merge_request.id) }
.not_to change { merge_request.reload.head_pipeline_id }
end end
end end
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment