Skip to content
Snippets Groups Projects
Commit faa9bd40 authored by Alejandro Rodríguez's avatar Alejandro Rodríguez
Browse files

Create a Gitlab::Git submodule for conlict-related files

Rename classes to (hopefully) clearer names while we're doing that.
parent 3fcab51e
No related branches found
No related tags found
No related merge requests found
Showing
with 304 additions and 293 deletions
Loading
@@ -53,7 +53,7 @@ class Projects::MergeRequests::ConflictsController < Projects::MergeRequests::Ap
Loading
@@ -53,7 +53,7 @@ class Projects::MergeRequests::ConflictsController < Projects::MergeRequests::Ap
flash[:notice] = 'All merge conflicts were resolved. The merge request can now be merged.' flash[:notice] = 'All merge conflicts were resolved. The merge request can now be merged.'
   
render json: { redirect_to: project_merge_request_url(@project, @merge_request, resolved_conflicts: true) } render json: { redirect_to: project_merge_request_url(@project, @merge_request, resolved_conflicts: true) }
rescue Gitlab::Git::Merge::ResolutionError => e rescue Gitlab::Git::Conflict::Resolver::ResolutionError => e
render status: :bad_request, json: { message: e.message } render status: :bad_request, json: { message: e.message }
end end
end end
Loading
Loading
Loading
@@ -23,7 +23,7 @@ module MergeRequests
Loading
@@ -23,7 +23,7 @@ module MergeRequests
# when there are no conflict files. # when there are no conflict files.
conflicts.files.each(&:lines) conflicts.files.each(&:lines)
@conflicts_can_be_resolved_in_ui = conflicts.files.length > 0 @conflicts_can_be_resolved_in_ui = conflicts.files.length > 0
rescue Rugged::OdbError, Gitlab::Git::ConflictParser::UnresolvableError, Gitlab::Git::Merge::ConflictSideMissing rescue Rugged::OdbError, Gitlab::Git::Conflict::Parser::UnresolvableError, Gitlab::Git::Conflict::Resolver::ConflictSideMissing
@conflicts_can_be_resolved_in_ui = false @conflicts_can_be_resolved_in_ui = false
end end
end end
Loading
Loading
Loading
@@ -186,7 +186,7 @@ module API
Loading
@@ -186,7 +186,7 @@ module API
   
lines.each do |line| lines.each do |line|
next unless line.new_pos == params[:line] && line.type == params[:line_type] next unless line.new_pos == params[:line] && line.type == params[:line_type]
break opts[:line_code] = Gitlab::Git::DiffLineCode.generate(diff.new_path, line.new_pos, line.old_pos) break opts[:line_code] = Gitlab::Git::Conflict::LineCode.generate(diff.new_path, line.new_pos, line.old_pos)
end end
   
break if opts[:line_code] break if opts[:line_code]
Loading
Loading
Loading
@@ -173,7 +173,7 @@ module API
Loading
@@ -173,7 +173,7 @@ module API
   
lines.each do |line| lines.each do |line|
next unless line.new_pos == params[:line] && line.type == params[:line_type] next unless line.new_pos == params[:line] && line.type == params[:line_type]
break opts[:line_code] = Gitlab::Git::DiffLineCode.generate(diff.new_path, line.new_pos, line.old_pos) break opts[:line_code] = Gitlab::Git::Conflict::LineCode.generate(diff.new_path, line.new_pos, line.old_pos)
end end
   
break if opts[:line_code] break if opts[:line_code]
Loading
Loading
Loading
@@ -23,7 +23,7 @@ module Github
Loading
@@ -23,7 +23,7 @@ module Github
private private
   
def generate_line_code(line) def generate_line_code(line)
Gitlab::Git::DiffLineCode.generate(file_path, line.new_pos, line.old_pos) Gitlab::Git::Conflict::LineCode.generate(file_path, line.new_pos, line.old_pos)
end end
   
def on_diff? def on_diff?
Loading
Loading
Loading
@@ -241,7 +241,7 @@ module Gitlab
Loading
@@ -241,7 +241,7 @@ module Gitlab
end end
   
def generate_line_code(pr_comment) def generate_line_code(pr_comment)
Gitlab::Git::DiffLineCode.generate(pr_comment.file_path, pr_comment.new_pos, pr_comment.old_pos) Gitlab::Git::Conflict::LineCode.generate(pr_comment.file_path, pr_comment.new_pos, pr_comment.old_pos)
end end
   
def pull_request_comment_attributes(comment) def pull_request_comment_attributes(comment)
Loading
Loading
Loading
@@ -6,7 +6,10 @@ module Gitlab
Loading
@@ -6,7 +6,10 @@ module Gitlab
   
CONTEXT_LINES = 3 CONTEXT_LINES = 3
   
attr_reader :merge_request, :raw attr_reader :merge_request
# 'raw' holds the Gitlab::Git::Conflict::File that this instance wraps
attr_reader :raw
   
delegate :type, :content, :their_path, :our_path, :our_mode, :our_blob, :repository, to: :raw delegate :type, :content, :their_path, :our_path, :our_mode, :our_blob, :repository, to: :raw
   
Loading
@@ -107,7 +110,7 @@ module Gitlab
Loading
@@ -107,7 +110,7 @@ module Gitlab
end end
   
def line_code(line) def line_code(line)
Gitlab::Git::DiffLineCode.generate(our_path, line.new_pos, line.old_pos) Gitlab::Git::Conflict::LineCode.generate(our_path, line.new_pos, line.old_pos)
end end
   
def create_match_line(line) def create_match_line(line)
Loading
Loading
module Gitlab module Gitlab
module Conflict module Conflict
class FileCollection class FileCollection
attr_reader :merge_request, :merge attr_reader :merge_request, :resolver
   
def initialize(merge_request) def initialize(merge_request)
source_repo = merge_request.source_project.repository.raw source_repo = merge_request.source_project.repository.raw
our_commit = merge_request.source_branch_head.raw our_commit = merge_request.source_branch_head.raw
their_commit = merge_request.target_branch_head.raw their_commit = merge_request.target_branch_head.raw
target_repo = merge_request.target_project.repository.raw target_repo = merge_request.target_project.repository.raw
@merge = Gitlab::Git::Merge.new(source_repo, our_commit, target_repo, their_commit) @resolver = Gitlab::Git::Conflict::Resolver.new(source_repo, our_commit, target_repo, their_commit)
@merge_request = merge_request @merge_request = merge_request
end end
   
Loading
@@ -18,11 +18,11 @@ module Gitlab
Loading
@@ -18,11 +18,11 @@ module Gitlab
target_branch: merge_request.target_branch, target_branch: merge_request.target_branch,
commit_message: commit_message || default_commit_message commit_message: commit_message || default_commit_message
} }
merge.resolve_conflicts(user, files, args) resolver.resolve_conflicts(user, files, args)
end end
   
def files def files
@files ||= merge.conflicts.map do |conflict_file| @files ||= resolver.conflicts.map do |conflict_file|
Gitlab::Conflict::File.new(conflict_file, merge_request: merge_request) Gitlab::Conflict::File.new(conflict_file, merge_request: merge_request)
end end
end end
Loading
Loading
Loading
@@ -49,7 +49,7 @@ module Gitlab
Loading
@@ -49,7 +49,7 @@ module Gitlab
def line_code(line) def line_code(line)
return if line.meta? return if line.meta?
   
Gitlab::Git::DiffLineCode.generate(file_path, line.new_pos, line.old_pos) Gitlab::Git::Conflict::LineCode.generate(file_path, line.new_pos, line.old_pos)
end end
   
def line_for_line_code(code) def line_for_line_code(code)
Loading
Loading
module Gitlab
module Git
module Conflict
class File
attr_reader :content, :their_path, :our_path, :our_mode, :repository
def initialize(repository, commit_oid, conflict, content)
@repository = repository
@commit_oid = commit_oid
@their_path = conflict[:theirs][:path]
@our_path = conflict[:ours][:path]
@our_mode = conflict[:ours][:mode]
@content = content
end
def lines
return @lines if defined?(@lines)
begin
@type = 'text'
@lines = Gitlab::Git::Conflict::Parser.parse(content,
our_path: our_path,
their_path: their_path)
rescue Gitlab::Git::Conflict::Parser::ParserError
@type = 'text-editor'
@lines = nil
end
end
def type
lines unless @type
@type.inquiry
end
def our_blob
# REFACTOR NOTE: the source of `commit_oid` used to be
# `merge_request.diff_refs.head_sha`. Instead of passing this value
# around the new lib structure, I decided to use `@commit_oid` which is
# equivalent to `merge_request.source_branch_head.raw.rugged_commit.oid`.
# That is what `merge_request.diff_refs.head_sha` is equivalent to when
# `merge_request` is not persisted (see `MergeRequest#diff_head_commit`).
# I think using the same oid is more consistent anyways, but if Conflicts
# start breaking, the change described above is a good place to look at.
@our_blob ||= repository.blob_at(@commit_oid, our_path)
end
def line_code(line)
Gitlab::Git::Conflict::LineCode.generate(our_path, line[:line_new], line[:line_old])
end
def resolve_lines(resolution)
section_id = nil
lines.map do |line|
unless line[:type]
section_id = nil
next line
end
section_id ||= line_code(line)
case resolution[section_id]
when 'head'
next unless line[:type] == 'new'
when 'origin'
next unless line[:type] == 'old'
else
raise Gitlab::Git::Conflict::Resolver::ResolutionError, "Missing resolution for section ID: #{section_id}"
end
line
end.compact
end
def resolve_content(resolution)
if resolution == content
raise Gitlab::Git::Conflict::Resolver::ResolutionError, "Resolved content has no changes for file #{our_path}"
end
resolution
end
end
end
end
end
module Gitlab
module Git
module Conflict
class LineCode
def self.generate(file_path, new_line_position, old_line_position)
"#{Digest::SHA1.hexdigest(file_path)}_#{old_line_position}_#{new_line_position}"
end
end
end
end
end
module Gitlab
module Git
module Conflict
class Parser
UnresolvableError = Class.new(StandardError)
UnmergeableFile = Class.new(UnresolvableError)
UnsupportedEncoding = Class.new(UnresolvableError)
# Recoverable errors - the conflict can be resolved in an editor, but not with
# sections.
ParserError = Class.new(StandardError)
UnexpectedDelimiter = Class.new(ParserError)
MissingEndDelimiter = Class.new(ParserError)
class << self
def parse(text, our_path:, their_path:, parent_file: nil)
validate_text!(text)
line_obj_index = 0
line_old = 1
line_new = 1
type = nil
lines = []
conflict_start = "<<<<<<< #{our_path}"
conflict_middle = '======='
conflict_end = ">>>>>>> #{their_path}"
text.each_line.map do |line|
full_line = line.delete("\n")
if full_line == conflict_start
validate_delimiter!(type.nil?)
type = 'new'
elsif full_line == conflict_middle
validate_delimiter!(type == 'new')
type = 'old'
elsif full_line == conflict_end
validate_delimiter!(type == 'old')
type = nil
elsif line[0] == '\\'
type = 'nonewline'
lines << {
full_line: full_line,
type: type,
line_obj_index: line_obj_index,
line_old: line_old,
line_new: line_new
}
else
lines << {
full_line: full_line,
type: type,
line_obj_index: line_obj_index,
line_old: line_old,
line_new: line_new
}
line_old += 1 if type != 'new'
line_new += 1 if type != 'old'
line_obj_index += 1
end
end
raise MissingEndDelimiter unless type.nil?
lines
end
private
def validate_text!(text)
raise UnmergeableFile if text.blank? # Typically a binary file
raise UnmergeableFile if text.length > 200.kilobytes
text.force_encoding('UTF-8')
raise UnsupportedEncoding unless text.valid_encoding?
end
def validate_delimiter!(condition)
raise UnexpectedDelimiter unless condition
end
end
end
end
end
end
module Gitlab
module Git
module Conflict
class Resolver
ConflictSideMissing = Class.new(StandardError)
ResolutionError = Class.new(StandardError)
def initialize(repository, our_commit, target_repository, their_commit)
@repository = repository
@our_commit = our_commit.rugged_commit
@target_repository = target_repository
@their_commit = their_commit.rugged_commit
end
def conflicts
@conflicts ||= begin
target_index = @target_repository.rugged.merge_commits(@our_commit, @their_commit)
# We don't need to do `with_repo_branch_commit` here, because the target
# project always fetches source refs when creating merge request diffs.
target_index.conflicts.map do |conflict|
raise ConflictSideMissing unless conflict[:theirs] && conflict[:ours]
Gitlab::Git::Conflict::File.new(
@target_repository,
@our_commit.oid,
conflict,
target_index.merge_file(conflict[:ours][:path])[:data]
)
end
end
end
def resolve_conflicts(user, files, source_branch:, target_branch:, commit_message:)
@repository.with_repo_branch_commit(@target_repository, target_branch) do
files.each do |file_params|
conflict_file = conflict_for_path(file_params[:old_path], file_params[:new_path])
write_resolved_file_to_index(conflict_file, file_params)
end
unless index.conflicts.empty?
missing_files = index.conflicts.map { |file| file[:ours][:path] }
raise ResolutionError, "Missing resolutions for the following files: #{missing_files.join(', ')}"
end
commit_params = {
message: commit_message,
parents: [@our_commit, @their_commit].map(&:oid)
}
@repository.commit_index(user, source_branch, index, commit_params)
end
end
def conflict_for_path(old_path, new_path)
conflicts.find do |conflict|
conflict.their_path == old_path && conflict.our_path == new_path
end
end
private
# We can only write when getting the merge index from the source
# project, because we will write to that project. We don't use this all
# the time because this fetches a ref into the source project, which
# isn't needed for reading.
def index
@index ||= @repository.rugged.merge_commits(@our_commit, @their_commit)
end
def write_resolved_file_to_index(file, params)
if params[:sections]
resolved_lines = file.resolve_lines(params[:sections])
new_file = resolved_lines.map { |line| line[:full_line] }.join("\n")
new_file << "\n" if file.our_blob.data.ends_with?("\n")
elsif params[:content]
new_file = file.resolve_content(params[:content])
end
our_path = file.our_path
index.add(path: our_path, oid: @repository.rugged.write(new_file, :blob), mode: file.our_mode)
index.conflict_remove(our_path)
end
end
end
end
end
module Gitlab
module Git
class ConflictFile
attr_reader :content, :their_path, :our_path, :our_mode, :repository
def initialize(repository, commit_oid, conflict, content)
@repository = repository
@commit_oid = commit_oid
@their_path = conflict[:theirs][:path]
@our_path = conflict[:ours][:path]
@our_mode = conflict[:ours][:mode]
@content = content
end
def lines
return @lines if defined?(@lines)
begin
@type = 'text'
@lines = Gitlab::Git::ConflictParser.parse(content,
our_path: our_path,
their_path: their_path)
rescue Gitlab::Git::ConflictParser::ParserError
@type = 'text-editor'
@lines = nil
end
end
def type
lines unless @type
@type.inquiry
end
def our_blob
# REFACTOR NOTE: the source of `commit_oid` used to be
# `merge_request.diff_refs.head_sha`. Instead of passing this value
# around the new lib structure, I decided to use `@commit_oid` which is
# equivalent to `merge_request.source_branch_head.raw.rugged_commit.oid`.
# That is what `merge_request.diff_refs.head_sha` is equivalent to when
# `merge_request` is not persisted (see `MergeRequest#diff_head_commit`).
# I think using the same oid is more consistent anyways, but if Conflicts
# start breaking, the change described above is a good place to look at.
@our_blob ||= repository.blob_at(@commit_oid, our_path)
end
def line_code(line)
Gitlab::Git::DiffLineCode.generate(our_path, line[:line_new], line[:line_old])
end
def resolve_lines(resolution)
section_id = nil
lines.map do |line|
unless line[:type]
section_id = nil
next line
end
section_id ||= line_code(line)
case resolution[section_id]
when 'head'
next unless line[:type] == 'new'
when 'origin'
next unless line[:type] == 'old'
else
raise Gitlab::Git::Merge::ResolutionError, "Missing resolution for section ID: #{section_id}"
end
line
end.compact
end
def resolve_content(resolution)
if resolution == content
raise Gitlab::Git::Merge::ResolutionError, "Resolved content has no changes for file #{our_path}"
end
resolution
end
end
end
end
module Gitlab
module Git
class ConflictParser
UnresolvableError = Class.new(StandardError)
UnmergeableFile = Class.new(UnresolvableError)
UnsupportedEncoding = Class.new(UnresolvableError)
# Recoverable errors - the conflict can be resolved in an editor, but not with
# sections.
ParserError = Class.new(StandardError)
UnexpectedDelimiter = Class.new(ParserError)
MissingEndDelimiter = Class.new(ParserError)
class << self
def parse(text, our_path:, their_path:, parent_file: nil)
validate_text!(text)
line_obj_index = 0
line_old = 1
line_new = 1
type = nil
lines = []
conflict_start = "<<<<<<< #{our_path}"
conflict_middle = '======='
conflict_end = ">>>>>>> #{their_path}"
text.each_line.map do |line|
full_line = line.delete("\n")
if full_line == conflict_start
validate_delimiter!(type.nil?)
type = 'new'
elsif full_line == conflict_middle
validate_delimiter!(type == 'new')
type = 'old'
elsif full_line == conflict_end
validate_delimiter!(type == 'old')
type = nil
elsif line[0] == '\\'
type = 'nonewline'
lines << {
full_line: full_line,
type: type,
line_obj_index: line_obj_index,
line_old: line_old,
line_new: line_new
}
else
lines << {
full_line: full_line,
type: type,
line_obj_index: line_obj_index,
line_old: line_old,
line_new: line_new
}
line_old += 1 if type != 'new'
line_new += 1 if type != 'old'
line_obj_index += 1
end
end
raise MissingEndDelimiter unless type.nil?
lines
end
private
def validate_text!(text)
raise UnmergeableFile if text.blank? # Typically a binary file
raise UnmergeableFile if text.length > 200.kilobytes
text.force_encoding('UTF-8')
raise UnsupportedEncoding unless text.valid_encoding?
end
def validate_delimiter!(condition)
raise UnexpectedDelimiter unless condition
end
end
end
end
end
module Gitlab
module Git
class DiffLineCode
def self.generate(file_path, new_line_position, old_line_position)
"#{Digest::SHA1.hexdigest(file_path)}_#{old_line_position}_#{new_line_position}"
end
end
end
end
module Gitlab
module Git
class Merge
ConflictSideMissing = Class.new(StandardError)
ResolutionError = Class.new(StandardError)
def initialize(repository, our_commit, target_repository, their_commit)
@repository = repository
@our_commit = our_commit.rugged_commit
@target_repository = target_repository
@their_commit = their_commit.rugged_commit
end
def conflicts
@conflicts ||= begin
target_index = @target_repository.rugged.merge_commits(@our_commit, @their_commit)
# We don't need to do `with_repo_branch_commit` here, because the target
# project always fetches source refs when creating merge request diffs.
target_index.conflicts.map do |conflict|
raise ConflictSideMissing unless conflict[:theirs] && conflict[:ours]
Gitlab::Git::ConflictFile.new(
@target_repository,
@our_commit.oid,
conflict,
target_index.merge_file(conflict[:ours][:path])[:data]
)
end
end
end
def resolve_conflicts(user, files, source_branch:, target_branch:, commit_message:)
@repository.with_repo_branch_commit(@target_repository, target_branch) do
files.each do |file_params|
conflict_file = conflict_for_path(file_params[:old_path], file_params[:new_path])
write_resolved_file_to_index(conflict_file, file_params)
end
unless index.conflicts.empty?
missing_files = index.conflicts.map { |file| file[:ours][:path] }
raise ResolutionError, "Missing resolutions for the following files: #{missing_files.join(', ')}"
end
commit_params = {
message: commit_message,
parents: [@our_commit, @their_commit].map(&:oid)
}
@repository.commit_index(user, source_branch, index, commit_params)
end
end
def conflict_for_path(old_path, new_path)
conflicts.find do |conflict|
conflict.their_path == old_path && conflict.our_path == new_path
end
end
private
# We can only write when getting the merge index from the source
# project, because we will write to that project. We don't use this all
# the time because this fetches a ref into the source project, which
# isn't needed for reading.
def index
@index ||= @repository.rugged.merge_commits(@our_commit, @their_commit)
end
def write_resolved_file_to_index(file, params)
if params[:sections]
resolved_lines = file.resolve_lines(params[:sections])
new_file = resolved_lines.map { |line| line[:full_line] }.join("\n")
new_file << "\n" if file.our_blob.data.ends_with?("\n")
elsif params[:content]
new_file = file.resolve_content(params[:content])
end
our_path = file.our_path
index.add(path: our_path, oid: @repository.rugged.write(new_file, :blob), mode: file.our_mode)
index.conflict_remove(our_path)
end
end
end
end
Loading
@@ -38,7 +38,7 @@ module Gitlab
Loading
@@ -38,7 +38,7 @@ module Gitlab
end end
   
def generate_line_code(line) def generate_line_code(line)
Gitlab::Git::DiffLineCode.generate(file_path, line.new_pos, line.old_pos) Gitlab::Git::Conflict::LineCode.generate(file_path, line.new_pos, line.old_pos)
end end
   
def on_diff? def on_diff?
Loading
Loading
Loading
@@ -17,8 +17,8 @@ describe Projects::MergeRequests::ConflictsController do
Loading
@@ -17,8 +17,8 @@ describe Projects::MergeRequests::ConflictsController do
describe 'GET show' do describe 'GET show' do
context 'when the conflicts cannot be resolved in the UI' do context 'when the conflicts cannot be resolved in the UI' do
before do before do
allow(Gitlab::Git::ConflictParser).to receive(:parse) allow(Gitlab::Git::Conflict::Parser).to receive(:parse)
.and_raise(Gitlab::Git::ConflictParser::UnmergeableFile) .and_raise(Gitlab::Git::Conflict::Parser::UnmergeableFile)
   
get :show, get :show,
namespace_id: merge_request_with_conflicts.project.namespace.to_param, namespace_id: merge_request_with_conflicts.project.namespace.to_param,
Loading
@@ -109,8 +109,8 @@ describe Projects::MergeRequests::ConflictsController do
Loading
@@ -109,8 +109,8 @@ describe Projects::MergeRequests::ConflictsController do
   
context 'when the conflicts cannot be resolved in the UI' do context 'when the conflicts cannot be resolved in the UI' do
before do before do
allow(Gitlab::Git::ConflictParser).to receive(:parse) allow(Gitlab::Git::Conflict::Parser).to receive(:parse)
.and_raise(Gitlab::Git::ConflictParser::UnmergeableFile) .and_raise(Gitlab::Git::Conflict::Parser::UnmergeableFile)
   
conflict_for_path('files/ruby/regex.rb') conflict_for_path('files/ruby/regex.rb')
end end
Loading
Loading
Loading
@@ -10,7 +10,7 @@ describe Gitlab::Conflict::File do
Loading
@@ -10,7 +10,7 @@ describe Gitlab::Conflict::File do
let(:index) { rugged.merge_commits(our_commit, their_commit) } let(:index) { rugged.merge_commits(our_commit, their_commit) }
let(:rugged_conflict) { index.conflicts.last } let(:rugged_conflict) { index.conflicts.last }
let(:raw_conflict_content) { index.merge_file('files/ruby/regex.rb')[:data] } let(:raw_conflict_content) { index.merge_file('files/ruby/regex.rb')[:data] }
let(:raw_conflict_file) { Gitlab::Git::ConflictFile.new(repository, our_commit.oid, rugged_conflict, raw_conflict_content) } let(:raw_conflict_file) { Gitlab::Git::Conflict::File.new(repository, our_commit.oid, rugged_conflict, raw_conflict_content) }
let(:conflict_file) { described_class.new(raw_conflict_file, merge_request: merge_request) } let(:conflict_file) { described_class.new(raw_conflict_file, merge_request: merge_request) }
   
describe '#resolve_lines' do describe '#resolve_lines' do
Loading
@@ -54,13 +54,13 @@ describe Gitlab::Conflict::File do
Loading
@@ -54,13 +54,13 @@ describe Gitlab::Conflict::File do
invalid_hash = section_keys.map { |key| [key, 'invalid'] }.to_h invalid_hash = section_keys.map { |key| [key, 'invalid'] }.to_h
   
expect { conflict_file.resolve_lines({}) } expect { conflict_file.resolve_lines({}) }
.to raise_error(Gitlab::Git::Merge::ResolutionError) .to raise_error(Gitlab::Git::Conflict::Resolver::ResolutionError)
   
expect { conflict_file.resolve_lines(empty_hash) } expect { conflict_file.resolve_lines(empty_hash) }
.to raise_error(Gitlab::Git::Merge::ResolutionError) .to raise_error(Gitlab::Git::Conflict::Resolver::ResolutionError)
   
expect { conflict_file.resolve_lines(invalid_hash) } expect { conflict_file.resolve_lines(invalid_hash) }
.to raise_error(Gitlab::Git::Merge::ResolutionError) .to raise_error(Gitlab::Git::Conflict::Resolver::ResolutionError)
end end
end end
   
Loading
Loading
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment