Commit 4a7a2fd1 authored by Michael Kozono's avatar Michael Kozono

Merge branch 'pb-refactor-process-ref-changes' into 'master'

Refactor processing of ref changes in PostReceive

See merge request gitlab-org/gitlab!18310
parents f5c009c5 7993542b
# frozen_string_literal: true
module Git
class ProcessRefChangesService < BaseService
PIPELINE_PROCESS_LIMIT = 4
def execute
changes = params[:changes]
process_changes_by_action(:branch, changes.branch_changes)
process_changes_by_action(:tag, changes.tag_changes)
end
private
def process_changes_by_action(ref_type, changes)
changes_by_action = group_changes_by_action(changes)
changes_by_action.each do |_, changes|
process_changes(ref_type, changes) if changes.any?
end
end
def group_changes_by_action(changes)
changes.group_by do |change|
change_action(change)
end
end
def change_action(change)
return :created if Gitlab::Git.blank_ref?(change[:oldrev])
return :removed if Gitlab::Git.blank_ref?(change[:newrev])
:pushed
end
def process_changes(ref_type, changes)
push_service_class = push_service_class_for(ref_type)
changes.each do |change|
push_service_class.new(
project,
current_user,
change: change,
push_options: params[:push_options],
create_pipelines: change[:index] < PIPELINE_PROCESS_LIMIT || Feature.enabled?(:git_push_create_all_pipelines, project)
).execute
end
end
def push_service_class_for(ref_type)
return Git::TagPushService if ref_type == :tag
Git::BranchPushService
end
end
end
...@@ -3,8 +3,6 @@ ...@@ -3,8 +3,6 @@
class PostReceive class PostReceive
include ApplicationWorker include ApplicationWorker
PIPELINE_PROCESS_LIMIT = 4
def perform(gl_repository, identifier, changes, push_options = {}) def perform(gl_repository, identifier, changes, push_options = {})
project, repo_type = Gitlab::GlRepository.parse(gl_repository) project, repo_type = Gitlab::GlRepository.parse(gl_repository)
...@@ -49,8 +47,7 @@ class PostReceive ...@@ -49,8 +47,7 @@ class PostReceive
expire_caches(post_received, post_received.project.repository) expire_caches(post_received, post_received.project.repository)
enqueue_repository_cache_update(post_received) enqueue_repository_cache_update(post_received)
process_changes(Git::BranchPushService, project, user, push_options, changes.branch_changes) process_ref_changes(project, user, push_options: push_options, changes: changes)
process_changes(Git::TagPushService, project, user, push_options, changes.tag_changes)
update_remote_mirrors(post_received) update_remote_mirrors(post_received)
after_project_changes_hooks(project, user, changes.refs, changes.repository_data) after_project_changes_hooks(project, user, changes.refs, changes.repository_data)
end end
...@@ -75,18 +72,10 @@ class PostReceive ...@@ -75,18 +72,10 @@ class PostReceive
) )
end end
def process_changes(service_class, project, user, push_options, changes) def process_ref_changes(project, user, params = {})
return if changes.empty? return unless params[:changes].any?
changes.each do |change| Git::ProcessRefChangesService.new(project, user, params).execute
service_class.new(
project,
user,
change: change,
push_options: push_options,
create_pipelines: change[:index] < PIPELINE_PROCESS_LIMIT || Feature.enabled?(:git_push_create_all_pipelines, project)
).execute
end
end end
def update_remote_mirrors(post_received) def update_remote_mirrors(post_received)
......
# frozen_string_literal: true
require 'spec_helper'
describe Git::ProcessRefChangesService do
let(:project) { create(:project, :repository) }
let(:user) { project.owner }
let(:params) { { changes: git_changes } }
subject { described_class.new(project, user, params) }
shared_examples_for 'service for processing ref changes' do |push_service_class|
let(:service) { double(execute: true) }
let(:git_changes) { double(branch_changes: [], tag_changes: []) }
let(:changes) do
[
{ index: 0, oldrev: Gitlab::Git::BLANK_SHA, newrev: '789012', ref: "#{ref_prefix}/create" },
{ index: 1, oldrev: '123456', newrev: '789012', ref: "#{ref_prefix}/update" },
{ index: 2, oldrev: '123456', newrev: Gitlab::Git::BLANK_SHA, ref: "#{ref_prefix}/delete" }
]
end
before do
allow(git_changes).to receive(changes_method).and_return(changes)
end
it "calls #{push_service_class}" do
expect(push_service_class)
.to receive(:new)
.exactly(changes.count).times
.and_return(service)
subject.execute
end
context 'pipeline creation' do
context 'with valid .gitlab-ci.yml' do
before do
stub_ci_pipeline_to_return_yaml_file
allow_any_instance_of(Project)
.to receive(:commit)
.and_return(project.commit)
allow_any_instance_of(Repository)
.to receive(:branch_exists?)
.and_return(true)
end
context 'when git_push_create_all_pipelines is disabled' do
before do
stub_feature_flags(git_push_create_all_pipelines: false)
end
it 'creates pipeline for branches and tags' do
subject.execute
expect(Ci::Pipeline.pluck(:ref)).to contain_exactly('create', 'update', 'delete')
end
it "creates exactly #{described_class::PIPELINE_PROCESS_LIMIT} pipelines" do
stub_const("#{described_class}::PIPELINE_PROCESS_LIMIT", changes.count - 1)
expect { subject.execute }.to change { Ci::Pipeline.count }.by(described_class::PIPELINE_PROCESS_LIMIT)
end
end
context 'when git_push_create_all_pipelines is enabled' do
before do
stub_feature_flags(git_push_create_all_pipelines: true)
end
it 'creates all pipelines' do
expect { subject.execute }.to change { Ci::Pipeline.count }.by(changes.count)
end
end
end
context 'with invalid .gitlab-ci.yml' do
before do
stub_ci_pipeline_yaml_file(nil)
end
it 'does not create a pipeline' do
expect { subject.execute }.not_to change { Ci::Pipeline.count }
end
end
end
end
context 'branch changes' do
let(:changes_method) { :branch_changes }
let(:ref_prefix) { 'refs/heads' }
it_behaves_like 'service for processing ref changes', Git::BranchPushService
end
context 'tag changes' do
let(:changes_method) { :tag_changes }
let(:ref_prefix) { 'refs/tags' }
it_behaves_like 'service for processing ref changes', Git::TagPushService
end
end
...@@ -73,8 +73,7 @@ describe PostReceive do ...@@ -73,8 +73,7 @@ describe PostReceive do
context 'empty changes' do context 'empty changes' do
it "does not call any PushService but runs after project hooks" do it "does not call any PushService but runs after project hooks" do
expect(Git::BranchPushService).not_to receive(:new) expect(Git::ProcessRefChangesService).not_to receive(:new)
expect(Git::TagPushService).not_to receive(:new)
expect_next_instance_of(SystemHooksService) { |service| expect(service).to receive(:execute_hooks) } expect_next_instance_of(SystemHooksService) { |service| expect(service).to receive(:execute_hooks) }
perform(changes: "") perform(changes: "")
...@@ -87,8 +86,7 @@ describe PostReceive do ...@@ -87,8 +86,7 @@ describe PostReceive do
let!(:key_id) { "" } let!(:key_id) { "" }
it 'returns false' do it 'returns false' do
expect(Git::BranchPushService).not_to receive(:new) expect(Git::ProcessRefChangesService).not_to receive(:new)
expect(Git::TagPushService).not_to receive(:new)
expect(perform).to be false expect(perform).to be false
end end
...@@ -131,13 +129,11 @@ describe PostReceive do ...@@ -131,13 +129,11 @@ describe PostReceive do
perform perform
end end
it 'calls Git::BranchPushService' do it 'calls Git::ProcessRefChangesService' do
expect_any_instance_of(Git::BranchPushService) do |service| expect_next_instance_of(Git::ProcessRefChangesService) do |service|
expect(service).to receive(:execute).and_return(true) expect(service).to receive(:execute).and_return(true)
end end
expect(Git::TagPushService).not_to receive(:new)
perform perform
end end
...@@ -174,8 +170,6 @@ describe PostReceive do ...@@ -174,8 +170,6 @@ describe PostReceive do
654321 210987 refs/tags/tag1 654321 210987 refs/tags/tag1
654322 210986 refs/tags/tag2 654322 210986 refs/tags/tag2
654323 210985 refs/tags/tag3 654323 210985 refs/tags/tag3
654324 210984 refs/tags/tag4
654325 210983 refs/tags/tag5
EOF EOF
end end
...@@ -189,23 +183,19 @@ describe PostReceive do ...@@ -189,23 +183,19 @@ describe PostReceive do
perform perform
end end
it "only invalidates tags once" do it 'only invalidates tags once' do
expect(project.repository).to receive(:repository_event).exactly(5).times.with(:push_tag).and_call_original expect(project.repository).to receive(:repository_event).exactly(3).times.with(:push_tag).and_call_original
expect(project.repository).to receive(:expire_caches_for_tags).once.and_call_original expect(project.repository).to receive(:expire_caches_for_tags).once.and_call_original
expect(project.repository).to receive(:expire_tags_cache).once.and_call_original expect(project.repository).to receive(:expire_tags_cache).once.and_call_original
perform perform
end end
it "calls Git::TagPushService" do it 'calls Git::ProcessRefChangesService' do
expect(Git::BranchPushService).not_to receive(:new) expect_next_instance_of(Git::ProcessRefChangesService) do |service|
expect_any_instance_of(Git::TagPushService) do |service|
expect(service).to receive(:execute).and_return(true) expect(service).to receive(:execute).and_return(true)
end end
expect(Git::BranchPushService).not_to receive(:new)
perform perform
end end
...@@ -223,8 +213,7 @@ describe PostReceive do ...@@ -223,8 +213,7 @@ describe PostReceive do
let(:changes) { "123456 789012 refs/merge-requests/123" } let(:changes) { "123456 789012 refs/merge-requests/123" }
it "does not call any of the services" do it "does not call any of the services" do
expect(Git::BranchPushService).not_to receive(:new) expect(Git::ProcessRefChangesService).not_to receive(:new)
expect(Git::TagPushService).not_to receive(:new)
perform perform
end end
...@@ -232,72 +221,6 @@ describe PostReceive do ...@@ -232,72 +221,6 @@ describe PostReceive do
it_behaves_like 'not updating remote mirrors' it_behaves_like 'not updating remote mirrors'
end end
context "gitlab-ci.yml" do
let(:changes) do
<<-EOF.strip_heredoc
123456 789012 refs/heads/feature
654321 210987 refs/tags/tag
123456 789012 refs/heads/feature2
123458 789013 refs/heads/feature3
123459 789015 refs/heads/feature4
EOF
end
let(:changes_count) { changes.lines.count }
subject { perform }
context "with valid .gitlab-ci.yml" do
before do
stub_ci_pipeline_to_return_yaml_file
allow_any_instance_of(Project)
.to receive(:commit)
.and_return(project.commit)
allow_any_instance_of(Repository)
.to receive(:branch_exists?)
.and_return(true)
end
context 'when git_push_create_all_pipelines is disabled' do
before do
stub_feature_flags(git_push_create_all_pipelines: false)
end
it "creates pipeline for branches and tags" do
subject
expect(Ci::Pipeline.pluck(:ref)).to contain_exactly("feature", "tag", "feature2", "feature3")
end
it "creates exactly #{described_class::PIPELINE_PROCESS_LIMIT} pipelines" do
expect(changes_count).to be > described_class::PIPELINE_PROCESS_LIMIT
expect { subject }.to change { Ci::Pipeline.count }.by(described_class::PIPELINE_PROCESS_LIMIT)
end
end
context 'when git_push_create_all_pipelines is enabled' do
before do
stub_feature_flags(git_push_create_all_pipelines: true)
end
it "creates all pipelines" do
expect { subject }.to change { Ci::Pipeline.count }.by(changes_count)
end
end
end
context "does not create a Ci::Pipeline" do
before do
stub_ci_pipeline_yaml_file(nil)
end
it { expect { subject }.not_to change { Ci::Pipeline.count } }
end
end
context 'after project changes hooks' do context 'after project changes hooks' do
let(:changes) { '123456 789012 refs/heads/tést' } let(:changes) { '123456 789012 refs/heads/tést' }
let(:fake_hook_data) { Hash.new(event_name: 'repository_update') } let(:fake_hook_data) { Hash.new(event_name: 'repository_update') }
...@@ -306,7 +229,7 @@ describe PostReceive do ...@@ -306,7 +229,7 @@ describe PostReceive do
allow_any_instance_of(Gitlab::DataBuilder::Repository).to receive(:update).and_return(fake_hook_data) allow_any_instance_of(Gitlab::DataBuilder::Repository).to receive(:update).and_return(fake_hook_data)
# silence hooks so we can isolate # silence hooks so we can isolate
allow_any_instance_of(Key).to receive(:post_create_hook).and_return(true) allow_any_instance_of(Key).to receive(:post_create_hook).and_return(true)
expect_next_instance_of(Git::BranchPushService) do |service| expect_next_instance_of(Git::ProcessRefChangesService) do |service|
expect(service).to receive(:execute).and_return(true) expect(service).to receive(:execute).and_return(true)
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