Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
path: root/spec
diff options
context:
space:
mode:
authorDouwe Maan <douwe@selenight.nl>2016-08-17 20:21:30 +0300
committerRuben Davila <rdavila84@gmail.com>2016-08-17 23:13:52 +0300
commit45cd11010dc9f63656640aa5d740748266fcf8ea (patch)
treea797a575220971fbe44945fdfbffaf1960c9911a /spec
parent1010284798d265cd11a2912da176bf78eee60004 (diff)
Merge branch 'mc-ui'
# Conflicts: # app/controllers/projects/merge_requests_controller.rb
Diffstat (limited to 'spec')
-rw-r--r--spec/controllers/projects/merge_requests_controller_spec.rb136
-rw-r--r--spec/features/merge_requests/conflicts_spec.rb72
-rw-r--r--spec/lib/gitlab/conflict/file_collection_spec.rb24
-rw-r--r--spec/lib/gitlab/conflict/file_spec.rb261
-rw-r--r--spec/lib/gitlab/conflict/parser_spec.rb188
-rw-r--r--spec/models/merge_request_spec.rb52
-rw-r--r--spec/support/test_env.rb44
7 files changed, 758 insertions, 19 deletions
diff --git a/spec/controllers/projects/merge_requests_controller_spec.rb b/spec/controllers/projects/merge_requests_controller_spec.rb
index 69758494543..c64c2b075c5 100644
--- a/spec/controllers/projects/merge_requests_controller_spec.rb
+++ b/spec/controllers/projects/merge_requests_controller_spec.rb
@@ -4,6 +4,11 @@ describe Projects::MergeRequestsController do
let(:project) { create(:project) }
let(:user) { create(:user) }
let(:merge_request) { create(:merge_request_with_diffs, target_project: project, source_project: project) }
+ let(:merge_request_with_conflicts) do
+ create(:merge_request, source_branch: 'conflict-resolvable', target_branch: 'conflict-start', source_project: project) do |mr|
+ mr.mark_as_unmergeable
+ end
+ end
before do
sign_in(user)
@@ -523,4 +528,135 @@ describe Projects::MergeRequestsController do
end
end
end
+
+ describe 'GET conflicts' do
+ let(:json_response) { JSON.parse(response.body) }
+
+ context 'when the conflicts cannot be resolved in the UI' do
+ before do
+ allow_any_instance_of(Gitlab::Conflict::Parser).
+ to receive(:parse).and_raise(Gitlab::Conflict::Parser::UnexpectedDelimiter)
+
+ get :conflicts,
+ namespace_id: merge_request_with_conflicts.project.namespace.to_param,
+ project_id: merge_request_with_conflicts.project.to_param,
+ id: merge_request_with_conflicts.iid,
+ format: 'json'
+ end
+
+ it 'returns a 200 status code' do
+ expect(response).to have_http_status(:ok)
+ end
+
+ it 'returns JSON with a message' do
+ expect(json_response.keys).to contain_exactly('message', 'type')
+ end
+ end
+
+ context 'with valid conflicts' do
+ before do
+ get :conflicts,
+ namespace_id: merge_request_with_conflicts.project.namespace.to_param,
+ project_id: merge_request_with_conflicts.project.to_param,
+ id: merge_request_with_conflicts.iid,
+ format: 'json'
+ end
+
+ it 'includes meta info about the MR' do
+ expect(json_response['commit_message']).to include('Merge branch')
+ expect(json_response['commit_sha']).to match(/\h{40}/)
+ expect(json_response['source_branch']).to eq(merge_request_with_conflicts.source_branch)
+ expect(json_response['target_branch']).to eq(merge_request_with_conflicts.target_branch)
+ end
+
+ it 'includes each file that has conflicts' do
+ filenames = json_response['files'].map { |file| file['new_path'] }
+
+ expect(filenames).to contain_exactly('files/ruby/popen.rb', 'files/ruby/regex.rb')
+ end
+
+ it 'splits files into sections with lines' do
+ json_response['files'].each do |file|
+ file['sections'].each do |section|
+ expect(section).to include('conflict', 'lines')
+
+ section['lines'].each do |line|
+ if section['conflict']
+ expect(line['type']).to be_in(['old', 'new'])
+ expect(line.values_at('old_line', 'new_line')).to contain_exactly(nil, a_kind_of(Integer))
+ else
+ if line['type'].nil?
+ expect(line['old_line']).not_to eq(nil)
+ expect(line['new_line']).not_to eq(nil)
+ else
+ expect(line['type']).to eq('match')
+ expect(line['old_line']).to eq(nil)
+ expect(line['new_line']).to eq(nil)
+ end
+ end
+ end
+ end
+ end
+ end
+
+ it 'has unique section IDs across files' do
+ section_ids = json_response['files'].flat_map do |file|
+ file['sections'].map { |section| section['id'] }.compact
+ end
+
+ expect(section_ids.uniq).to eq(section_ids)
+ end
+ end
+ end
+
+ context 'POST resolve_conflicts' do
+ let(:json_response) { JSON.parse(response.body) }
+ let!(:original_head_sha) { merge_request_with_conflicts.diff_head_sha }
+
+ def resolve_conflicts(sections)
+ post :resolve_conflicts,
+ namespace_id: merge_request_with_conflicts.project.namespace.to_param,
+ project_id: merge_request_with_conflicts.project.to_param,
+ id: merge_request_with_conflicts.iid,
+ format: 'json',
+ sections: sections,
+ commit_message: 'Commit message'
+ end
+
+ context 'with valid params' do
+ before do
+ resolve_conflicts('2f6fcd96b88b36ce98c38da085c795a27d92a3dd_14_14' => 'head',
+ '6eb14e00385d2fb284765eb1cd8d420d33d63fc9_9_9' => 'head',
+ '6eb14e00385d2fb284765eb1cd8d420d33d63fc9_21_21' => 'origin',
+ '6eb14e00385d2fb284765eb1cd8d420d33d63fc9_49_49' => 'origin')
+ end
+
+ it 'creates a new commit on the branch' do
+ expect(original_head_sha).not_to eq(merge_request_with_conflicts.source_branch_head.sha)
+ expect(merge_request_with_conflicts.source_branch_head.message).to include('Commit message')
+ end
+
+ it 'returns an OK response' do
+ expect(response).to have_http_status(:ok)
+ end
+ end
+
+ context 'when sections are missing' do
+ before do
+ resolve_conflicts('2f6fcd96b88b36ce98c38da085c795a27d92a3dd_14_14' => 'head')
+ end
+
+ it 'returns a 400 error' do
+ expect(response).to have_http_status(:bad_request)
+ end
+
+ it 'has a message with the name of the first missing section' do
+ expect(json_response['message']).to include('6eb14e00385d2fb284765eb1cd8d420d33d63fc9_9_9')
+ end
+
+ it 'does not create a new commit' do
+ expect(original_head_sha).to eq(merge_request_with_conflicts.source_branch_head.sha)
+ end
+ end
+ end
end
diff --git a/spec/features/merge_requests/conflicts_spec.rb b/spec/features/merge_requests/conflicts_spec.rb
new file mode 100644
index 00000000000..930c36ade2b
--- /dev/null
+++ b/spec/features/merge_requests/conflicts_spec.rb
@@ -0,0 +1,72 @@
+require 'spec_helper'
+
+feature 'Merge request conflict resolution', js: true, feature: true do
+ include WaitForAjax
+
+ let(:user) { create(:user) }
+ let(:project) { create(:project) }
+
+ def create_merge_request(source_branch)
+ create(:merge_request, source_branch: source_branch, target_branch: 'conflict-start', source_project: project) do |mr|
+ mr.mark_as_unmergeable
+ end
+ end
+
+ context 'when a merge request can be resolved in the UI' do
+ let(:merge_request) { create_merge_request('conflict-resolvable') }
+
+ before do
+ project.team << [user, :developer]
+ login_as(user)
+
+ visit namespace_project_merge_request_path(project.namespace, project, merge_request)
+ end
+
+ it 'shows a link to the conflict resolution page' do
+ expect(page).to have_link('conflicts', href: /\/conflicts\Z/)
+ end
+
+ context 'visiting the conflicts resolution page' do
+ before { click_link('conflicts', href: /\/conflicts\Z/) }
+
+ it 'shows the conflicts' do
+ begin
+ expect(find('#conflicts')).to have_content('popen.rb')
+ rescue Capybara::Poltergeist::JavascriptError
+ retry
+ end
+ end
+ end
+ end
+
+ UNRESOLVABLE_CONFLICTS = {
+ 'conflict-too-large' => 'when the conflicts contain a large file',
+ 'conflict-binary-file' => 'when the conflicts contain a binary file',
+ 'conflict-contains-conflict-markers' => 'when the conflicts contain a file with ambiguous conflict markers',
+ 'conflict-missing-side' => 'when the conflicts contain a file edited in one branch and deleted in another'
+ }
+
+ UNRESOLVABLE_CONFLICTS.each do |source_branch, description|
+ context description do
+ let(:merge_request) { create_merge_request(source_branch) }
+
+ before do
+ project.team << [user, :developer]
+ login_as(user)
+
+ visit namespace_project_merge_request_path(project.namespace, project, merge_request)
+ end
+
+ it 'does not show a link to the conflict resolution page' do
+ expect(page).not_to have_link('conflicts', href: /\/conflicts\Z/)
+ end
+
+ it 'shows an error if the conflicts page is visited directly' do
+ visit current_url + '/conflicts'
+ wait_for_ajax
+
+ expect(find('#conflicts')).to have_content('Please try to resolve them locally.')
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/conflict/file_collection_spec.rb b/spec/lib/gitlab/conflict/file_collection_spec.rb
new file mode 100644
index 00000000000..39d892c18c0
--- /dev/null
+++ b/spec/lib/gitlab/conflict/file_collection_spec.rb
@@ -0,0 +1,24 @@
+require 'spec_helper'
+
+describe Gitlab::Conflict::FileCollection, lib: true do
+ let(:merge_request) { create(:merge_request, source_branch: 'conflict-resolvable', target_branch: 'conflict-start') }
+ let(:file_collection) { Gitlab::Conflict::FileCollection.new(merge_request) }
+
+ describe '#files' do
+ it 'returns an array of Conflict::Files' do
+ expect(file_collection.files).to all(be_an_instance_of(Gitlab::Conflict::File))
+ end
+ end
+
+ describe '#default_commit_message' do
+ it 'matches the format of the git CLI commit message' do
+ expect(file_collection.default_commit_message).to eq(<<EOM.chomp)
+Merge branch 'conflict-start' into 'conflict-resolvable'
+
+# Conflicts:
+# files/ruby/popen.rb
+# files/ruby/regex.rb
+EOM
+ end
+ end
+end
diff --git a/spec/lib/gitlab/conflict/file_spec.rb b/spec/lib/gitlab/conflict/file_spec.rb
new file mode 100644
index 00000000000..60020487061
--- /dev/null
+++ b/spec/lib/gitlab/conflict/file_spec.rb
@@ -0,0 +1,261 @@
+require 'spec_helper'
+
+describe Gitlab::Conflict::File, lib: true do
+ let(:project) { create(:project) }
+ let(:repository) { project.repository }
+ let(:rugged) { repository.rugged }
+ let(:their_commit) { rugged.branches['conflict-start'].target }
+ let(:our_commit) { rugged.branches['conflict-resolvable'].target }
+ let(:merge_request) { create(:merge_request, source_branch: 'conflict-resolvable', target_branch: 'conflict-start', source_project: project) }
+ let(:index) { rugged.merge_commits(our_commit, their_commit) }
+ let(:conflict) { index.conflicts.last }
+ let(:merge_file_result) { index.merge_file('files/ruby/regex.rb') }
+ let(:conflict_file) { Gitlab::Conflict::File.new(merge_file_result, conflict, merge_request: merge_request) }
+
+ describe '#resolve_lines' do
+ let(:section_keys) { conflict_file.sections.map { |section| section[:id] }.compact }
+
+ context 'when resolving everything to the same side' do
+ let(:resolution_hash) { section_keys.map { |key| [key, 'head'] }.to_h }
+ let(:resolved_lines) { conflict_file.resolve_lines(resolution_hash) }
+ let(:expected_lines) { conflict_file.lines.reject { |line| line.type == 'old' } }
+
+ it 'has the correct number of lines' do
+ expect(resolved_lines.length).to eq(expected_lines.length)
+ end
+
+ it 'has content matching the chosen lines' do
+ expect(resolved_lines.map(&:text)).to eq(expected_lines.map(&:text))
+ end
+ end
+
+ context 'with mixed resolutions' do
+ let(:resolution_hash) do
+ section_keys.map.with_index { |key, i| [key, i.even? ? 'head' : 'origin'] }.to_h
+ end
+
+ let(:resolved_lines) { conflict_file.resolve_lines(resolution_hash) }
+
+ it 'has the correct number of lines' do
+ file_lines = conflict_file.lines.reject { |line| line.type == 'new' }
+
+ expect(resolved_lines.length).to eq(file_lines.length)
+ end
+
+ it 'returns a file containing only the chosen parts of the resolved sections' do
+ expect(resolved_lines.chunk { |line| line.type || 'both' }.map(&:first)).
+ to eq(['both', 'new', 'both', 'old', 'both', 'new', 'both'])
+ end
+ end
+
+ it 'raises MissingResolution when passed a hash without resolutions for all sections' do
+ empty_hash = section_keys.map { |key| [key, nil] }.to_h
+ invalid_hash = section_keys.map { |key| [key, 'invalid'] }.to_h
+
+ expect { conflict_file.resolve_lines({}) }.
+ to raise_error(Gitlab::Conflict::File::MissingResolution)
+
+ expect { conflict_file.resolve_lines(empty_hash) }.
+ to raise_error(Gitlab::Conflict::File::MissingResolution)
+
+ expect { conflict_file.resolve_lines(invalid_hash) }.
+ to raise_error(Gitlab::Conflict::File::MissingResolution)
+ end
+ end
+
+ describe '#highlight_lines!' do
+ def html_to_text(html)
+ CGI.unescapeHTML(ActionView::Base.full_sanitizer.sanitize(html)).delete("\n")
+ end
+
+ it 'modifies the existing lines' do
+ expect { conflict_file.highlight_lines! }.to change { conflict_file.lines.map(&:instance_variables) }
+ end
+
+ it 'is called implicitly when rich_text is accessed on a line' do
+ expect(conflict_file).to receive(:highlight_lines!).once.and_call_original
+
+ conflict_file.lines.each(&:rich_text)
+ end
+
+ it 'sets the rich_text of the lines matching the text content' do
+ conflict_file.lines.each do |line|
+ expect(line.text).to eq(html_to_text(line.rich_text))
+ end
+ end
+ end
+
+ describe '#sections' do
+ it 'only inserts match lines when there is a gap between sections' do
+ conflict_file.sections.each_with_index do |section, i|
+ previous_line_number = 0
+ current_line_number = section[:lines].map(&:old_line).compact.min
+
+ if i > 0
+ previous_line_number = conflict_file.sections[i - 1][:lines].map(&:old_line).compact.last
+ end
+
+ if current_line_number == previous_line_number + 1
+ expect(section[:lines].first.type).not_to eq('match')
+ else
+ expect(section[:lines].first.type).to eq('match')
+ expect(section[:lines].first.text).to match(/\A@@ -#{current_line_number},\d+ \+\d+,\d+ @@ module Gitlab\Z/)
+ end
+ end
+ end
+
+ it 'sets conflict to false for sections with only unchanged lines' do
+ conflict_file.sections.reject { |section| section[:conflict] }.each do |section|
+ without_match = section[:lines].reject { |line| line.type == 'match' }
+
+ expect(without_match).to all(have_attributes(type: nil))
+ end
+ end
+
+ it 'only includes a maximum of CONTEXT_LINES (plus an optional match line) in context sections' do
+ conflict_file.sections.reject { |section| section[:conflict] }.each do |section|
+ without_match = section[:lines].reject { |line| line.type == 'match' }
+
+ expect(without_match.length).to be <= Gitlab::Conflict::File::CONTEXT_LINES * 2
+ end
+ end
+
+ it 'sets conflict to true for sections with only changed lines' do
+ conflict_file.sections.select { |section| section[:conflict] }.each do |section|
+ section[:lines].each do |line|
+ expect(line.type).to be_in(['new', 'old'])
+ end
+ end
+ end
+
+ it 'adds unique IDs to conflict sections, and not to other sections' do
+ section_ids = []
+
+ conflict_file.sections.each do |section|
+ if section[:conflict]
+ expect(section).to have_key(:id)
+ section_ids << section[:id]
+ else
+ expect(section).not_to have_key(:id)
+ end
+ end
+
+ expect(section_ids.uniq).to eq(section_ids)
+ end
+
+ context 'with an example file' do
+ let(:file) do
+ <<FILE
+ # Ensure there is no match line header here
+ def username_regexp
+ default_regexp
+ end
+
+<<<<<<< files/ruby/regex.rb
+def project_name_regexp
+ /\A[a-zA-Z0-9][a-zA-Z0-9_\-\. ]*\z/
+end
+
+def name_regexp
+ /\A[a-zA-Z0-9_\-\. ]*\z/
+=======
+def project_name_regex
+ %r{\A[a-zA-Z0-9][a-zA-Z0-9_\-\. ]*\z}
+end
+
+def name_regex
+ %r{\A[a-zA-Z0-9_\-\. ]*\z}
+>>>>>>> files/ruby/regex.rb
+end
+
+# Some extra lines
+# To force a match line
+# To be created
+
+def path_regexp
+ default_regexp
+end
+
+<<<<<<< files/ruby/regex.rb
+def archive_formats_regexp
+ /(zip|tar|7z|tar\.gz|tgz|gz|tar\.bz2|tbz|tbz2|tb2|bz2)/
+=======
+def archive_formats_regex
+ %r{(zip|tar|7z|tar\.gz|tgz|gz|tar\.bz2|tbz|tbz2|tb2|bz2)}
+>>>>>>> files/ruby/regex.rb
+end
+
+def git_reference_regexp
+ # Valid git ref regexp, see:
+ # https://www.kernel.org/pub/software/scm/git/docs/git-check-ref-format.html
+ %r{
+ (?!
+ (?# doesn't begins with)
+ \/| (?# rule #6)
+ (?# doesn't contain)
+ .*(?:
+ [\/.]\.| (?# rule #1,3)
+ \/\/| (?# rule #6)
+ @\{| (?# rule #8)
+ \\ (?# rule #9)
+ )
+ )
+ [^\000-\040\177~^:?*\[]+ (?# rule #4-5)
+ (?# doesn't end with)
+ (?<!\.lock) (?# rule #1)
+ (?<![\/.]) (?# rule #6-7)
+ }x
+end
+
+protected
+
+<<<<<<< files/ruby/regex.rb
+def default_regexp
+ /\A[.?]?[a-zA-Z0-9][a-zA-Z0-9_\-\.]*(?<!\.git)\z/
+=======
+def default_regex
+ %r{\A[.?]?[a-zA-Z0-9][a-zA-Z0-9_\-\.]*(?<!\.git)\z}
+>>>>>>> files/ruby/regex.rb
+end
+FILE
+ end
+
+ let(:conflict_file) { Gitlab::Conflict::File.new({ data: file }, conflict, merge_request: merge_request) }
+ let(:sections) { conflict_file.sections }
+
+ it 'sets the correct match line headers' do
+ expect(sections[0][:lines].first).to have_attributes(type: 'match', text: '@@ -3,14 +3,14 @@')
+ expect(sections[3][:lines].first).to have_attributes(type: 'match', text: '@@ -19,26 +19,26 @@ def path_regexp')
+ expect(sections[6][:lines].first).to have_attributes(type: 'match', text: '@@ -47,52 +47,52 @@ end')
+ end
+
+ it 'does not add match lines where they are not needed' do
+ expect(sections[1][:lines].first.type).not_to eq('match')
+ expect(sections[2][:lines].first.type).not_to eq('match')
+ expect(sections[4][:lines].first.type).not_to eq('match')
+ expect(sections[5][:lines].first.type).not_to eq('match')
+ expect(sections[7][:lines].first.type).not_to eq('match')
+ end
+
+ it 'creates context sections of the correct length' do
+ expect(sections[0][:lines].reject(&:type).length).to eq(3)
+ expect(sections[2][:lines].reject(&:type).length).to eq(3)
+ expect(sections[3][:lines].reject(&:type).length).to eq(3)
+ expect(sections[5][:lines].reject(&:type).length).to eq(3)
+ expect(sections[6][:lines].reject(&:type).length).to eq(3)
+ expect(sections[8][:lines].reject(&:type).length).to eq(1)
+ end
+ end
+ end
+
+ describe '#as_json' do
+ it 'includes the blob path for the file' do
+ expect(conflict_file.as_json[:blob_path]).
+ to eq("/#{project.namespace.to_param}/#{merge_request.project.to_param}/blob/#{our_commit.oid}/files/ruby/regex.rb")
+ end
+
+ it 'includes the blob icon for the file' do
+ expect(conflict_file.as_json[:blob_icon]).to eq('file-text-o')
+ end
+ end
+end
diff --git a/spec/lib/gitlab/conflict/parser_spec.rb b/spec/lib/gitlab/conflict/parser_spec.rb
new file mode 100644
index 00000000000..65a828accde
--- /dev/null
+++ b/spec/lib/gitlab/conflict/parser_spec.rb
@@ -0,0 +1,188 @@
+require 'spec_helper'
+
+describe Gitlab::Conflict::Parser, lib: true do
+ let(:parser) { Gitlab::Conflict::Parser.new }
+
+ describe '#parse' do
+ def parse_text(text)
+ parser.parse(text, our_path: 'README.md', their_path: 'README.md')
+ end
+
+ context 'when the file has valid conflicts' do
+ let(:text) do
+ <<CONFLICT
+module Gitlab
+ module Regexp
+ extend self
+
+ def username_regexp
+ default_regexp
+ end
+
+<<<<<<< files/ruby/regex.rb
+ def project_name_regexp
+ /\A[a-zA-Z0-9][a-zA-Z0-9_\-\. ]*\z/
+ end
+
+ def name_regexp
+ /\A[a-zA-Z0-9_\-\. ]*\z/
+=======
+ def project_name_regex
+ %r{\A[a-zA-Z0-9][a-zA-Z0-9_\-\. ]*\z}
+ end
+
+ def name_regex
+ %r{\A[a-zA-Z0-9_\-\. ]*\z}
+>>>>>>> files/ruby/regex.rb
+ end
+
+ def path_regexp
+ default_regexp
+ end
+
+<<<<<<< files/ruby/regex.rb
+ def archive_formats_regexp
+ /(zip|tar|7z|tar\.gz|tgz|gz|tar\.bz2|tbz|tbz2|tb2|bz2)/
+=======
+ def archive_formats_regex
+ %r{(zip|tar|7z|tar\.gz|tgz|gz|tar\.bz2|tbz|tbz2|tb2|bz2)}
+>>>>>>> files/ruby/regex.rb
+ end
+
+ def git_reference_regexp
+ # Valid git ref regexp, see:
+ # https://www.kernel.org/pub/software/scm/git/docs/git-check-ref-format.html
+ %r{
+ (?!
+ (?# doesn't begins with)
+ \/| (?# rule #6)
+ (?# doesn't contain)
+ .*(?:
+ [\/.]\.| (?# rule #1,3)
+ \/\/| (?# rule #6)
+ @\{| (?# rule #8)
+ \\ (?# rule #9)
+ )
+ )
+ [^\000-\040\177~^:?*\[]+ (?# rule #4-5)
+ (?# doesn't end with)
+ (?<!\.lock) (?# rule #1)
+ (?<![\/.]) (?# rule #6-7)
+ }x
+ end
+
+ protected
+
+<<<<<<< files/ruby/regex.rb
+ def default_regexp
+ /\A[.?]?[a-zA-Z0-9][a-zA-Z0-9_\-\.]*(?<!\.git)\z/
+=======
+ def default_regex
+ %r{\A[.?]?[a-zA-Z0-9][a-zA-Z0-9_\-\.]*(?<!\.git)\z}
+>>>>>>> files/ruby/regex.rb
+ end
+ end
+end
+CONFLICT
+ end
+
+ let(:lines) do
+ parser.parse(text, our_path: 'files/ruby/regex.rb', their_path: 'files/ruby/regex.rb')
+ end
+
+ it 'sets our lines as new lines' do
+ expect(lines[8..13]).to all(have_attributes(type: 'new'))
+ expect(lines[26..27]).to all(have_attributes(type: 'new'))
+ expect(lines[56..57]).to all(have_attributes(type: 'new'))
+ end
+
+ it 'sets their lines as old lines' do
+ expect(lines[14..19]).to all(have_attributes(type: 'old'))
+ expect(lines[28..29]).to all(have_attributes(type: 'old'))
+ expect(lines[58..59]).to all(have_attributes(type: 'old'))
+ end
+
+ it 'sets non-conflicted lines as both' do
+ expect(lines[0..7]).to all(have_attributes(type: nil))
+ expect(lines[20..25]).to all(have_attributes(type: nil))
+ expect(lines[30..55]).to all(have_attributes(type: nil))
+ expect(lines[60..62]).to all(have_attributes(type: nil))
+ end
+
+ it 'sets consecutive line numbers for index, old_pos, and new_pos' do
+ old_line_numbers = lines.select { |line| line.type != 'new' }.map(&:old_pos)
+ new_line_numbers = lines.select { |line| line.type != 'old' }.map(&:new_pos)
+
+ expect(lines.map(&:index)).to eq(0.upto(62).to_a)
+ expect(old_line_numbers).to eq(1.upto(53).to_a)
+ expect(new_line_numbers).to eq(1.upto(53).to_a)
+ end
+ end
+
+ context 'when the file contents include conflict delimiters' do
+ it 'raises UnexpectedDelimiter when there is a non-start delimiter first' do
+ expect { parse_text('=======') }.
+ to raise_error(Gitlab::Conflict::Parser::UnexpectedDelimiter)
+
+ expect { parse_text('>>>>>>> README.md') }.
+ to raise_error(Gitlab::Conflict::Parser::UnexpectedDelimiter)
+
+ expect { parse_text('>>>>>>> some-other-path.md') }.
+ not_to raise_error
+ end
+
+ it 'raises UnexpectedDelimiter when a start delimiter is followed by a non-middle delimiter' do
+ start_text = "<<<<<<< README.md\n"
+ end_text = "\n=======\n>>>>>>> README.md"
+
+ expect { parse_text(start_text + '>>>>>>> README.md' + end_text) }.
+ to raise_error(Gitlab::Conflict::Parser::UnexpectedDelimiter)
+
+ expect { parse_text(start_text + start_text + end_text) }.
+ to raise_error(Gitlab::Conflict::Parser::UnexpectedDelimiter)
+
+ expect { parse_text(start_text + '>>>>>>> some-other-path.md' + end_text) }.
+ not_to raise_error
+ end
+
+ it 'raises UnexpectedDelimiter when a middle delimiter is followed by a non-end delimiter' do
+ start_text = "<<<<<<< README.md\n=======\n"
+ end_text = "\n>>>>>>> README.md"
+
+ expect { parse_text(start_text + '=======' + end_text) }.
+ to raise_error(Gitlab::Conflict::Parser::UnexpectedDelimiter)
+
+ expect { parse_text(start_text + start_text + end_text) }.
+ to raise_error(Gitlab::Conflict::Parser::UnexpectedDelimiter)
+
+ expect { parse_text(start_text + '>>>>>>> some-other-path.md' + end_text) }.
+ not_to raise_error
+ end
+
+ it 'raises MissingEndDelimiter when there is no end delimiter at the end' do
+ start_text = "<<<<<<< README.md\n=======\n"
+
+ expect { parse_text(start_text) }.
+ to raise_error(Gitlab::Conflict::Parser::MissingEndDelimiter)
+
+ expect { parse_text(start_text + '>>>>>>> some-other-path.md') }.
+ to raise_error(Gitlab::Conflict::Parser::MissingEndDelimiter)
+ end
+ end
+
+ context 'other file types' do
+ it 'raises UnmergeableFile when lines is blank, indicating a binary file' do
+ expect { parse_text('') }.
+ to raise_error(Gitlab::Conflict::Parser::UnmergeableFile)
+
+ expect { parse_text(nil) }.
+ to raise_error(Gitlab::Conflict::Parser::UnmergeableFile)
+ end
+
+ it 'raises UnmergeableFile when the file is over 100 KB' do
+ expect { parse_text('a' * 102401) }.
+ to raise_error(Gitlab::Conflict::Parser::UnmergeableFile)
+ end
+ end
+ end
+end
diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb
index acb75ec21a9..f83dbefedc0 100644
--- a/spec/models/merge_request_spec.rb
+++ b/spec/models/merge_request_spec.rb
@@ -783,4 +783,56 @@ describe MergeRequest, models: true do
end
end
end
+
+ describe '#conflicts_can_be_resolved_in_ui?' do
+ def create_merge_request(source_branch)
+ create(:merge_request, source_branch: source_branch, target_branch: 'conflict-start') do |mr|
+ mr.mark_as_unmergeable
+ end
+ end
+
+ it 'returns a falsey value when the MR can be merged without conflicts' do
+ merge_request = create_merge_request('master')
+ merge_request.mark_as_mergeable
+
+ expect(merge_request.conflicts_can_be_resolved_in_ui?).to be_falsey
+ end
+
+ it 'returns a falsey value when the MR does not support new diff notes' do
+ merge_request = create_merge_request('conflict-resolvable')
+ merge_request.merge_request_diff.update_attributes(start_commit_sha: nil)
+
+ expect(merge_request.conflicts_can_be_resolved_in_ui?).to be_falsey
+ end
+
+ it 'returns a falsey value when the conflicts contain a large file' do
+ merge_request = create_merge_request('conflict-too-large')
+
+ expect(merge_request.conflicts_can_be_resolved_in_ui?).to be_falsey
+ end
+
+ it 'returns a falsey value when the conflicts contain a binary file' do
+ merge_request = create_merge_request('conflict-binary-file')
+
+ expect(merge_request.conflicts_can_be_resolved_in_ui?).to be_falsey
+ end
+
+ it 'returns a falsey value when the conflicts contain a file with ambiguous conflict markers' do
+ merge_request = create_merge_request('conflict-contains-conflict-markers')
+
+ expect(merge_request.conflicts_can_be_resolved_in_ui?).to be_falsey
+ end
+
+ it 'returns a falsey value when the conflicts contain a file edited in one branch and deleted in another' do
+ merge_request = create_merge_request('conflict-missing-side')
+
+ expect(merge_request.conflicts_can_be_resolved_in_ui?).to be_falsey
+ end
+
+ it 'returns a truthy value when the conflicts are resolvable in the UI' do
+ merge_request = create_merge_request('conflict-resolvable')
+
+ expect(merge_request.conflicts_can_be_resolved_in_ui?).to be_truthy
+ end
+ end
end
diff --git a/spec/support/test_env.rb b/spec/support/test_env.rb
index 1c0c66969e3..edbbfc3c9e5 100644
--- a/spec/support/test_env.rb
+++ b/spec/support/test_env.rb
@@ -5,25 +5,31 @@ module TestEnv
# When developing the seed repository, comment out the branch you will modify.
BRANCH_SHA = {
- 'empty-branch' => '7efb185',
- 'ends-with.json' => '98b0d8b3',
- 'flatten-dir' => 'e56497b',
- 'feature' => '0b4bc9a',
- 'feature_conflict' => 'bb5206f',
- 'fix' => '48f0be4',
- 'improve/awesome' => '5937ac0',
- 'markdown' => '0ed8c6c',
- 'lfs' => 'be93687',
- 'master' => '5937ac0',
- "'test'" => 'e56497b',
- 'orphaned-branch' => '45127a9',
- 'binary-encoding' => '7b1cf43',
- 'gitattributes' => '5a62481',
- 'expand-collapse-diffs' => '4842455',
- 'expand-collapse-files' => '025db92',
- 'expand-collapse-lines' => '238e82d',
- 'video' => '8879059',
- 'crlf-diff' => '5938907'
+ 'empty-branch' => '7efb185',
+ 'ends-with.json' => '98b0d8b3',
+ 'flatten-dir' => 'e56497b',
+ 'feature' => '0b4bc9a',
+ 'feature_conflict' => 'bb5206f',
+ 'fix' => '48f0be4',
+ 'improve/awesome' => '5937ac0',
+ 'markdown' => '0ed8c6c',
+ 'lfs' => 'be93687',
+ 'master' => '5937ac0',
+ "'test'" => 'e56497b',
+ 'orphaned-branch' => '45127a9',
+ 'binary-encoding' => '7b1cf43',
+ 'gitattributes' => '5a62481',
+ 'expand-collapse-diffs' => '4842455',
+ 'expand-collapse-files' => '025db92',
+ 'expand-collapse-lines' => '238e82d',
+ 'video' => '8879059',
+ 'crlf-diff' => '5938907',
+ 'conflict-start' => '14fa46b',
+ 'conflict-resolvable' => '1450cd6',
+ 'conflict-binary-file' => '259a6fb',
+ 'conflict-contains-conflict-markers' => '5e0964c',
+ 'conflict-missing-side' => 'eb227b3',
+ 'conflict-too-large' => '39fa04f',
}
# gitlab-test-fork is a fork of gitlab-fork, but we don't necessarily