186 lines
6.7 KiB
Ruby
186 lines
6.7 KiB
Ruby
|
# frozen_string_literal: true
|
||
|
|
||
|
require 'spec_helper'
|
||
|
|
||
|
RSpec.describe Projects::Forks::SyncService, feature_category: :source_code_management do
|
||
|
include ProjectForksHelper
|
||
|
include RepoHelpers
|
||
|
|
||
|
let_it_be(:user) { create(:user) }
|
||
|
let_it_be(:source_project) { create(:project, :repository, :public) }
|
||
|
let_it_be(:project) { fork_project(source_project, user, { repository: true }) }
|
||
|
|
||
|
let(:fork_branch) { project.default_branch }
|
||
|
let(:service) { described_class.new(project, user, fork_branch) }
|
||
|
|
||
|
def details
|
||
|
Projects::Forks::Details.new(project, fork_branch)
|
||
|
end
|
||
|
|
||
|
def expect_to_cancel_exclusive_lease
|
||
|
expect(Gitlab::ExclusiveLease).to receive(:cancel)
|
||
|
end
|
||
|
|
||
|
describe '#execute' do
|
||
|
context 'when fork is up-to-date with the upstream' do
|
||
|
it 'does not perform merge' do
|
||
|
expect_to_cancel_exclusive_lease
|
||
|
expect(project.repository).not_to receive(:merge_to_branch)
|
||
|
expect(project.repository).not_to receive(:ff_merge)
|
||
|
|
||
|
expect(service.execute).to be_success
|
||
|
end
|
||
|
end
|
||
|
|
||
|
context 'when fork is behind the upstream' do
|
||
|
let_it_be(:base_commit) { source_project.commit.sha }
|
||
|
|
||
|
before_all do
|
||
|
source_project.repository.commit_files(
|
||
|
user,
|
||
|
branch_name: source_project.repository.root_ref, message: 'Commit to root ref',
|
||
|
actions: [{ action: :create, file_path: 'encoding/CHANGELOG', content: 'One more' }]
|
||
|
)
|
||
|
|
||
|
source_project.repository.commit_files(
|
||
|
user,
|
||
|
branch_name: source_project.repository.root_ref, message: 'Another commit to root ref',
|
||
|
actions: [{ action: :create, file_path: 'encoding/NEW-CHANGELOG', content: 'One more time' }]
|
||
|
)
|
||
|
end
|
||
|
|
||
|
before do
|
||
|
project.repository.create_branch(fork_branch, base_commit)
|
||
|
end
|
||
|
|
||
|
context 'when fork is not ahead of the upstream' do
|
||
|
let(:fork_branch) { 'fork-without-new-commits' }
|
||
|
|
||
|
it 'updates the fork using ff merge' do
|
||
|
expect_to_cancel_exclusive_lease
|
||
|
expect(project.commit(fork_branch).sha).to eq(base_commit)
|
||
|
expect(project.repository).to receive(:ff_merge)
|
||
|
.with(user, source_project.commit.sha, fork_branch, target_sha: base_commit)
|
||
|
.and_call_original
|
||
|
|
||
|
expect do
|
||
|
expect(service.execute).to be_success
|
||
|
end.to change { details.counts }.from({ ahead: 0, behind: 2 }).to({ ahead: 0, behind: 0 })
|
||
|
end
|
||
|
end
|
||
|
|
||
|
context 'when fork is ahead of the upstream' do
|
||
|
context 'and has conflicts with the upstream', :use_clean_rails_redis_caching do
|
||
|
let(:fork_branch) { 'fork-with-conflicts' }
|
||
|
|
||
|
it 'returns an error' do
|
||
|
project.repository.commit_files(
|
||
|
user,
|
||
|
branch_name: fork_branch, message: 'Committing something',
|
||
|
actions: [{ action: :create, file_path: 'encoding/CHANGELOG', content: 'New file' }]
|
||
|
)
|
||
|
|
||
|
expect_to_cancel_exclusive_lease
|
||
|
expect(details).not_to have_conflicts
|
||
|
|
||
|
expect do
|
||
|
result = service.execute
|
||
|
|
||
|
expect(result).to be_error
|
||
|
expect(result.message).to eq("9:merging commits: merge: there are conflicting files.")
|
||
|
end.not_to change { details.counts }
|
||
|
|
||
|
expect(details).to have_conflicts
|
||
|
end
|
||
|
end
|
||
|
|
||
|
context 'and does not have conflicts with the upstream' do
|
||
|
let(:fork_branch) { 'fork-with-new-commits' }
|
||
|
|
||
|
it 'updates the fork using merge' do
|
||
|
project.repository.commit_files(
|
||
|
user,
|
||
|
branch_name: fork_branch, message: 'Committing completely new changelog',
|
||
|
actions: [{ action: :create, file_path: 'encoding/COMPLETELY-NEW-CHANGELOG', content: 'New file' }]
|
||
|
)
|
||
|
|
||
|
commit_message = "Merge branch #{source_project.path}:#{source_project.default_branch} into #{fork_branch}"
|
||
|
expect(project.repository).to receive(:merge_to_branch).with(
|
||
|
user,
|
||
|
source_sha: source_project.commit.sha,
|
||
|
target_branch: fork_branch,
|
||
|
target_sha: project.commit(fork_branch).sha,
|
||
|
message: commit_message
|
||
|
).and_call_original
|
||
|
expect_to_cancel_exclusive_lease
|
||
|
|
||
|
expect do
|
||
|
expect(service.execute).to be_success
|
||
|
end.to change { details.counts }.from({ ahead: 1, behind: 2 }).to({ ahead: 2, behind: 0 })
|
||
|
|
||
|
commits = project.repository.commits_between(source_project.commit.sha, project.commit(fork_branch).sha)
|
||
|
expect(commits.map(&:message)).to eq([
|
||
|
"Committing completely new changelog",
|
||
|
commit_message
|
||
|
])
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
|
||
|
context 'when a merge cannot happen due to another ongoing merge' do
|
||
|
it 'does not merge' do
|
||
|
expect(service).to receive(:perform_merge).and_return(nil)
|
||
|
|
||
|
result = service.execute
|
||
|
|
||
|
expect(result).to be_error
|
||
|
expect(result.message).to eq(described_class::ONGOING_MERGE_ERROR)
|
||
|
end
|
||
|
end
|
||
|
|
||
|
context 'when upstream branch contains lfs reference' do
|
||
|
let(:source_project) { create(:project, :repository, :public) }
|
||
|
let(:project) { fork_project(source_project, user, { repository: true }) }
|
||
|
let(:fork_branch) { 'fork-fetches-lfs-pointers' }
|
||
|
|
||
|
before do
|
||
|
source_project.change_head('lfs')
|
||
|
|
||
|
allow(source_project).to receive(:lfs_enabled?).and_return(true)
|
||
|
allow(project).to receive(:lfs_enabled?).and_return(true)
|
||
|
|
||
|
create_file_in_repo(source_project, 'lfs', 'lfs', 'one.lfs', 'One')
|
||
|
create_file_in_repo(source_project, 'lfs', 'lfs', 'two.lfs', 'Two')
|
||
|
end
|
||
|
|
||
|
it 'links fetched lfs objects to the fork project', :aggregate_failures do
|
||
|
expect_to_cancel_exclusive_lease
|
||
|
|
||
|
expect do
|
||
|
expect(service.execute).to be_success
|
||
|
end.to change { project.reload.lfs_objects.size }.from(0).to(2)
|
||
|
.and change { details.counts }.from({ ahead: 0, behind: 3 }).to({ ahead: 0, behind: 0 })
|
||
|
|
||
|
expect(project.lfs_objects).to match_array(source_project.lfs_objects)
|
||
|
end
|
||
|
|
||
|
context 'and there are too many of them for a single sync' do
|
||
|
let(:fork_branch) { 'fork-too-many-lfs-pointers' }
|
||
|
|
||
|
it 'updates the fork successfully' do
|
||
|
expect_to_cancel_exclusive_lease
|
||
|
stub_const('Projects::LfsPointers::LfsLinkService::MAX_OIDS', 1)
|
||
|
|
||
|
expect do
|
||
|
result = service.execute
|
||
|
|
||
|
expect(result).to be_error
|
||
|
expect(result.message).to eq('Too many LFS object ids to link, please push them manually')
|
||
|
end.not_to change { details.counts }
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
end
|