Fix resolving conflicts on forks

Forks may not be up-to-date with the target project, and so might not
contain one of the parent refs in their repo. Fetch this if it isn't
present.
This commit is contained in:
Sean McGivern 2016-08-29 12:43:09 +01:00
parent 2db570e92c
commit 1bda1e62de
4 changed files with 132 additions and 21 deletions

View file

@ -36,6 +36,9 @@ v 8.12.0 (unreleased)
- Use the default branch for displaying the project icon instead of master !5792 (Hannes Rosenögger)
- Adds response mime type to transaction metric action when it's not HTML
v 8.11.4 (unreleased)
- Fix resolving conflicts on forks
v 8.11.3 (unreleased)
- Allow system info page to handle case where info is unavailable
- Label list shows all issues (opened or closed) with that label

View file

@ -1,11 +1,14 @@
module MergeRequests
class ResolveService < MergeRequests::BaseService
attr_accessor :conflicts, :rugged, :merge_index
attr_accessor :conflicts, :rugged, :merge_index, :merge_request
def execute(merge_request)
@conflicts = merge_request.conflicts
@rugged = project.repository.rugged
@merge_index = conflicts.merge_index
@merge_request = merge_request
fetch_their_commit!
conflicts.files.each do |file|
write_resolved_file_to_index(file, params[:sections])
@ -27,5 +30,16 @@ module MergeRequests
merge_index.add(path: our_path, oid: rugged.write(new_file, :blob), mode: file.our_mode)
merge_index.conflict_remove(our_path)
end
# If their commit (in the target project) doesn't exist in the source project, it
# can't be a parent for the merge commit we're about to create. If that's the case,
# fetch the target branch ref into the source project so the commit exists in both.
#
def fetch_their_commit!
return if rugged.include?(conflicts.their_commit.oid)
remote = rugged.remotes.create_anonymous(merge_request.target_project.repository.path_to_repo)
remote.fetch(merge_request.target_branch)
end
end
end

View file

@ -0,0 +1,87 @@
require 'spec_helper'
describe MergeRequests::ResolveService do
let(:user) { create(:user) }
let(:project) { create(:project) }
let(:fork_project) do
create(:forked_project_with_submodules) do |fork_project|
fork_project.build_forked_project_link(forked_to_project_id: fork_project.id, forked_from_project_id: project.id)
fork_project.save
end
end
let(:merge_request) do
create(:merge_request,
source_branch: 'conflict-resolvable', source_project: project,
target_branch: 'conflict-start')
end
let(:merge_request_from_fork) do
create(:merge_request,
source_branch: 'conflict-resolvable-fork', source_project: fork_project,
target_branch: 'conflict-start', target_project: project)
end
describe '#execute' do
context 'with valid params' do
let(:params) do
{
sections: {
'2f6fcd96b88b36ce98c38da085c795a27d92a3dd_14_14' => 'head',
'6eb14e00385d2fb284765eb1cd8d420d33d63fc9_9_9' => 'head',
'6eb14e00385d2fb284765eb1cd8d420d33d63fc9_21_21' => 'origin',
'6eb14e00385d2fb284765eb1cd8d420d33d63fc9_49_49' => 'origin'
},
commit_message: 'This is a commit message!'
}
end
context 'when the source and target project are the same' do
before do
MergeRequests::ResolveService.new(project, user, params).execute(merge_request)
end
it 'creates a commit with the message' do
expect(merge_request.source_branch_head.message).to eq(params[:commit_message])
end
it 'creates a commit with the correct parents' do
expect(merge_request.source_branch_head.parents.map(&:id)).
to eq(['1450cd639e0bc6721eb02800169e464f212cde06',
'75284c70dd26c87f2a3fb65fd5a1f0b0138d3a6b'])
end
end
context 'when the source project is a fork and does not contain the HEAD of the target branch' do
let!(:target_head) do
project.repository.commit_file(user, 'new-file-in-target', '', 'Add new file in target', 'conflict-start', false)
end
before do
MergeRequests::ResolveService.new(fork_project, user, params).execute(merge_request_from_fork)
end
it 'creates a commit with the message' do
expect(merge_request_from_fork.source_branch_head.message).to eq(params[:commit_message])
end
it 'creates a commit with the correct parents' do
expect(merge_request_from_fork.source_branch_head.parents.map(&:id)).
to eq(['404fa3fc7c2c9b5dacff102f353bdf55b1be2813',
target_head])
end
end
end
context 'when a resolution is missing' do
let(:invalid_params) { { sections: { '2f6fcd96b88b36ce98c38da085c795a27d92a3dd_14_14' => 'head' } } }
let(:service) { MergeRequests::ResolveService.new(project, user, invalid_params) }
it 'raises a MissingResolution error' do
expect { service.execute(merge_request) }.
to raise_error(Gitlab::Conflict::File::MissingResolution)
end
end
end
end

View file

@ -6,7 +6,7 @@ module TestEnv
# When developing the seed repository, comment out the branch you will modify.
BRANCH_SHA = {
'empty-branch' => '7efb185',
'ends-with.json' => '98b0d8b3',
'ends-with.json' => '98b0d8b',
'flatten-dir' => 'e56497b',
'feature' => '0b4bc9a',
'feature_conflict' => 'bb5206f',
@ -37,9 +37,10 @@ module TestEnv
# need to keep all the branches in sync.
# We currently only need a subset of the branches
FORKED_BRANCH_SHA = {
'add-submodule-version-bump' => '3f547c08',
'master' => '5937ac0',
'remove-submodule' => '2a33e0c0'
'add-submodule-version-bump' => '3f547c0',
'master' => '5937ac0',
'remove-submodule' => '2a33e0c',
'conflict-resolvable-fork' => '404fa3f'
}
# Test environment
@ -117,22 +118,7 @@ module TestEnv
system(*%W(#{Gitlab.config.git.bin_path} clone -q #{clone_url} #{repo_path}))
end
Dir.chdir(repo_path) do
branch_sha.each do |branch, sha|
# Try to reset without fetching to avoid using the network.
reset = %W(#{Gitlab.config.git.bin_path} update-ref refs/heads/#{branch} #{sha})
unless system(*reset)
if system(*%W(#{Gitlab.config.git.bin_path} fetch origin))
unless system(*reset)
raise 'The fetched test seed '\
'does not contain the required revision.'
end
else
raise 'Could not fetch test seed repository.'
end
end
end
end
set_repo_refs(repo_path, branch_sha)
# We must copy bare repositories because we will push to them.
system(git_env, *%W(#{Gitlab.config.git.bin_path} clone -q --bare #{repo_path} #{repo_path_bare}))
@ -144,6 +130,7 @@ module TestEnv
FileUtils.mkdir_p(target_repo_path)
FileUtils.cp_r("#{base_repo_path}/.", target_repo_path)
FileUtils.chmod_R 0755, target_repo_path
set_repo_refs(target_repo_path, BRANCH_SHA)
end
def repos_path
@ -160,6 +147,7 @@ module TestEnv
FileUtils.mkdir_p(target_repo_path)
FileUtils.cp_r("#{base_repo_path}/.", target_repo_path)
FileUtils.chmod_R 0755, target_repo_path
set_repo_refs(target_repo_path, FORKED_BRANCH_SHA)
end
# When no cached assets exist, manually hit the root path to create them
@ -209,4 +197,23 @@ module TestEnv
def git_env
{ 'GIT_TEMPLATE_DIR' => '' }
end
def set_repo_refs(repo_path, branch_sha)
Dir.chdir(repo_path) do
branch_sha.each do |branch, sha|
# Try to reset without fetching to avoid using the network.
reset = %W(#{Gitlab.config.git.bin_path} update-ref refs/heads/#{branch} #{sha})
unless system(*reset)
if system(*%W(#{Gitlab.config.git.bin_path} fetch origin))
unless system(*reset)
raise 'The fetched test seed '\
'does not contain the required revision.'
end
else
raise 'Could not fetch test seed repository.'
end
end
end
end
end
end