Create a Gitlab::Git submodule for conlict-related files
Rename classes to (hopefully) clearer names while we're doing that.
This commit is contained in:
parent
3fcab51ebb
commit
faa9bd402d
24 changed files with 334 additions and 323 deletions
|
@ -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.'
|
||||
|
||||
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 }
|
||||
end
|
||||
end
|
||||
|
|
|
@ -23,7 +23,7 @@ module MergeRequests
|
|||
# when there are no conflict files.
|
||||
conflicts.files.each(&:lines)
|
||||
@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
|
||||
end
|
||||
end
|
||||
|
|
|
@ -186,7 +186,7 @@ module API
|
|||
|
||||
lines.each do |line|
|
||||
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
|
||||
|
||||
break if opts[:line_code]
|
||||
|
|
|
@ -173,7 +173,7 @@ module API
|
|||
|
||||
lines.each do |line|
|
||||
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
|
||||
|
||||
break if opts[:line_code]
|
||||
|
|
|
@ -23,7 +23,7 @@ module Github
|
|||
private
|
||||
|
||||
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
|
||||
|
||||
def on_diff?
|
||||
|
|
|
@ -241,7 +241,7 @@ module Gitlab
|
|||
end
|
||||
|
||||
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
|
||||
|
||||
def pull_request_comment_attributes(comment)
|
||||
|
|
|
@ -6,7 +6,10 @@ module Gitlab
|
|||
|
||||
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
|
||||
|
||||
|
@ -107,7 +110,7 @@ module Gitlab
|
|||
end
|
||||
|
||||
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
|
||||
|
||||
def create_match_line(line)
|
||||
|
|
|
@ -1,14 +1,14 @@
|
|||
module Gitlab
|
||||
module Conflict
|
||||
class FileCollection
|
||||
attr_reader :merge_request, :merge
|
||||
attr_reader :merge_request, :resolver
|
||||
|
||||
def initialize(merge_request)
|
||||
source_repo = merge_request.source_project.repository.raw
|
||||
our_commit = merge_request.source_branch_head.raw
|
||||
their_commit = merge_request.target_branch_head.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
|
||||
end
|
||||
|
||||
|
@ -18,11 +18,11 @@ module Gitlab
|
|||
target_branch: merge_request.target_branch,
|
||||
commit_message: commit_message || default_commit_message
|
||||
}
|
||||
merge.resolve_conflicts(user, files, args)
|
||||
resolver.resolve_conflicts(user, files, args)
|
||||
end
|
||||
|
||||
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)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -49,7 +49,7 @@ module Gitlab
|
|||
def line_code(line)
|
||||
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
|
||||
|
||||
def line_for_line_code(code)
|
||||
|
|
86
lib/gitlab/git/conflict/file.rb
Normal file
86
lib/gitlab/git/conflict/file.rb
Normal file
|
@ -0,0 +1,86 @@
|
|||
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
|
11
lib/gitlab/git/conflict/line_code.rb
Normal file
11
lib/gitlab/git/conflict/line_code.rb
Normal file
|
@ -0,0 +1,11 @@
|
|||
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
|
91
lib/gitlab/git/conflict/parser.rb
Normal file
91
lib/gitlab/git/conflict/parser.rb
Normal file
|
@ -0,0 +1,91 @@
|
|||
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
|
91
lib/gitlab/git/conflict/resolver.rb
Normal file
91
lib/gitlab/git/conflict/resolver.rb
Normal file
|
@ -0,0 +1,91 @@
|
|||
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
|
|
@ -1,84 +0,0 @@
|
|||
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
|
|
@ -1,89 +0,0 @@
|
|||
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
|
|
@ -1,9 +0,0 @@
|
|||
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
|
|
@ -1,89 +0,0 @@
|
|||
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
|
|
@ -38,7 +38,7 @@ module Gitlab
|
|||
end
|
||||
|
||||
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
|
||||
|
||||
def on_diff?
|
||||
|
|
|
@ -17,8 +17,8 @@ describe Projects::MergeRequests::ConflictsController do
|
|||
describe 'GET show' do
|
||||
context 'when the conflicts cannot be resolved in the UI' do
|
||||
before do
|
||||
allow(Gitlab::Git::ConflictParser).to receive(:parse)
|
||||
.and_raise(Gitlab::Git::ConflictParser::UnmergeableFile)
|
||||
allow(Gitlab::Git::Conflict::Parser).to receive(:parse)
|
||||
.and_raise(Gitlab::Git::Conflict::Parser::UnmergeableFile)
|
||||
|
||||
get :show,
|
||||
namespace_id: merge_request_with_conflicts.project.namespace.to_param,
|
||||
|
@ -109,8 +109,8 @@ describe Projects::MergeRequests::ConflictsController do
|
|||
|
||||
context 'when the conflicts cannot be resolved in the UI' do
|
||||
before do
|
||||
allow(Gitlab::Git::ConflictParser).to receive(:parse)
|
||||
.and_raise(Gitlab::Git::ConflictParser::UnmergeableFile)
|
||||
allow(Gitlab::Git::Conflict::Parser).to receive(:parse)
|
||||
.and_raise(Gitlab::Git::Conflict::Parser::UnmergeableFile)
|
||||
|
||||
conflict_for_path('files/ruby/regex.rb')
|
||||
end
|
||||
|
|
|
@ -10,7 +10,7 @@ describe Gitlab::Conflict::File do
|
|||
let(:index) { rugged.merge_commits(our_commit, their_commit) }
|
||||
let(:rugged_conflict) { index.conflicts.last }
|
||||
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) }
|
||||
|
||||
describe '#resolve_lines' do
|
||||
|
@ -54,13 +54,13 @@ describe Gitlab::Conflict::File do
|
|||
invalid_hash = section_keys.map { |key| [key, 'invalid'] }.to_h
|
||||
|
||||
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) }
|
||||
.to raise_error(Gitlab::Git::Merge::ResolutionError)
|
||||
.to raise_error(Gitlab::Git::Conflict::Resolver::ResolutionError)
|
||||
|
||||
expect { conflict_file.resolve_lines(invalid_hash) }
|
||||
.to raise_error(Gitlab::Git::Merge::ResolutionError)
|
||||
.to raise_error(Gitlab::Git::Conflict::Resolver::ResolutionError)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -40,7 +40,7 @@ describe Gitlab::Diff::Position do
|
|||
|
||||
describe "#line_code" do
|
||||
it "returns the correct line code" do
|
||||
line_code = Gitlab::Git::DiffLineCode.generate(subject.file_path, subject.new_line, 0)
|
||||
line_code = Gitlab::Git::Conflict::LineCode.generate(subject.file_path, subject.new_line, 0)
|
||||
|
||||
expect(subject.line_code(project.repository)).to eq(line_code)
|
||||
end
|
||||
|
@ -108,7 +108,7 @@ describe Gitlab::Diff::Position do
|
|||
|
||||
describe "#line_code" do
|
||||
it "returns the correct line code" do
|
||||
line_code = Gitlab::Git::DiffLineCode.generate(subject.file_path, subject.new_line, 15)
|
||||
line_code = Gitlab::Git::Conflict::LineCode.generate(subject.file_path, subject.new_line, 15)
|
||||
|
||||
expect(subject.line_code(project.repository)).to eq(line_code)
|
||||
end
|
||||
|
@ -149,7 +149,7 @@ describe Gitlab::Diff::Position do
|
|||
|
||||
describe "#line_code" do
|
||||
it "returns the correct line code" do
|
||||
line_code = Gitlab::Git::DiffLineCode.generate(subject.file_path, subject.new_line, subject.old_line)
|
||||
line_code = Gitlab::Git::Conflict::LineCode.generate(subject.file_path, subject.new_line, subject.old_line)
|
||||
|
||||
expect(subject.line_code(project.repository)).to eq(line_code)
|
||||
end
|
||||
|
@ -189,7 +189,7 @@ describe Gitlab::Diff::Position do
|
|||
|
||||
describe "#line_code" do
|
||||
it "returns the correct line code" do
|
||||
line_code = Gitlab::Git::DiffLineCode.generate(subject.file_path, 13, subject.old_line)
|
||||
line_code = Gitlab::Git::Conflict::LineCode.generate(subject.file_path, 13, subject.old_line)
|
||||
|
||||
expect(subject.line_code(project.repository)).to eq(line_code)
|
||||
end
|
||||
|
@ -233,7 +233,7 @@ describe Gitlab::Diff::Position do
|
|||
|
||||
describe "#line_code" do
|
||||
it "returns the correct line code" do
|
||||
line_code = Gitlab::Git::DiffLineCode.generate(subject.file_path, subject.new_line, 5)
|
||||
line_code = Gitlab::Git::Conflict::LineCode.generate(subject.file_path, subject.new_line, 5)
|
||||
|
||||
expect(subject.line_code(project.repository)).to eq(line_code)
|
||||
end
|
||||
|
@ -274,7 +274,7 @@ describe Gitlab::Diff::Position do
|
|||
|
||||
describe "#line_code" do
|
||||
it "returns the correct line code" do
|
||||
line_code = Gitlab::Git::DiffLineCode.generate(subject.file_path, subject.new_line, subject.old_line)
|
||||
line_code = Gitlab::Git::Conflict::LineCode.generate(subject.file_path, subject.new_line, subject.old_line)
|
||||
|
||||
expect(subject.line_code(project.repository)).to eq(line_code)
|
||||
end
|
||||
|
@ -314,7 +314,7 @@ describe Gitlab::Diff::Position do
|
|||
|
||||
describe "#line_code" do
|
||||
it "returns the correct line code" do
|
||||
line_code = Gitlab::Git::DiffLineCode.generate(subject.file_path, 4, subject.old_line)
|
||||
line_code = Gitlab::Git::Conflict::LineCode.generate(subject.file_path, 4, subject.old_line)
|
||||
|
||||
expect(subject.line_code(project.repository)).to eq(line_code)
|
||||
end
|
||||
|
@ -357,7 +357,7 @@ describe Gitlab::Diff::Position do
|
|||
|
||||
describe "#line_code" do
|
||||
it "returns the correct line code" do
|
||||
line_code = Gitlab::Git::DiffLineCode.generate(subject.file_path, 0, subject.old_line)
|
||||
line_code = Gitlab::Git::Conflict::LineCode.generate(subject.file_path, 0, subject.old_line)
|
||||
|
||||
expect(subject.line_code(project.repository)).to eq(line_code)
|
||||
end
|
||||
|
@ -399,7 +399,7 @@ describe Gitlab::Diff::Position do
|
|||
|
||||
describe "#line_code" do
|
||||
it "returns the correct line code" do
|
||||
line_code = Gitlab::Git::DiffLineCode.generate(subject.file_path, subject.new_line, 0)
|
||||
line_code = Gitlab::Git::Conflict::LineCode.generate(subject.file_path, subject.new_line, 0)
|
||||
|
||||
expect(subject.line_code(project.repository)).to eq(line_code)
|
||||
end
|
||||
|
@ -447,7 +447,7 @@ describe Gitlab::Diff::Position do
|
|||
|
||||
describe "#line_code" do
|
||||
it "returns the correct line code" do
|
||||
line_code = Gitlab::Git::DiffLineCode.generate(subject.file_path, 0, subject.old_line)
|
||||
line_code = Gitlab::Git::Conflict::LineCode.generate(subject.file_path, 0, subject.old_line)
|
||||
|
||||
expect(subject.line_code(project.repository)).to eq(line_code)
|
||||
end
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
require 'spec_helper'
|
||||
|
||||
describe Gitlab::Git::ConflictParser do
|
||||
describe Gitlab::Git::Conflict::Parser do
|
||||
describe '.parse' do
|
||||
def parse_text(text)
|
||||
described_class.parse(text, our_path: 'README.md', their_path: 'README.md')
|
||||
|
@ -125,12 +125,12 @@ CONFLICT
|
|||
context 'when there is a non-start delimiter first' do
|
||||
it 'raises UnexpectedDelimiter when there is a middle delimiter first' do
|
||||
expect { parse_text('=======') }
|
||||
.to raise_error(Gitlab::Git::ConflictParser::UnexpectedDelimiter)
|
||||
.to raise_error(Gitlab::Git::Conflict::Parser::UnexpectedDelimiter)
|
||||
end
|
||||
|
||||
it 'raises UnexpectedDelimiter when there is an end delimiter first' do
|
||||
expect { parse_text('>>>>>>> README.md') }
|
||||
.to raise_error(Gitlab::Git::ConflictParser::UnexpectedDelimiter)
|
||||
.to raise_error(Gitlab::Git::Conflict::Parser::UnexpectedDelimiter)
|
||||
end
|
||||
|
||||
it 'does not raise when there is an end delimiter for a different path first' do
|
||||
|
@ -145,12 +145,12 @@ CONFLICT
|
|||
|
||||
it 'raises UnexpectedDelimiter when it is followed by an end delimiter' do
|
||||
expect { parse_text(start_text + '>>>>>>> README.md' + end_text) }
|
||||
.to raise_error(Gitlab::Git::ConflictParser::UnexpectedDelimiter)
|
||||
.to raise_error(Gitlab::Git::Conflict::Parser::UnexpectedDelimiter)
|
||||
end
|
||||
|
||||
it 'raises UnexpectedDelimiter when it is followed by another start delimiter' do
|
||||
expect { parse_text(start_text + start_text + end_text) }
|
||||
.to raise_error(Gitlab::Git::ConflictParser::UnexpectedDelimiter)
|
||||
.to raise_error(Gitlab::Git::Conflict::Parser::UnexpectedDelimiter)
|
||||
end
|
||||
|
||||
it 'does not raise when it is followed by a start delimiter for a different path' do
|
||||
|
@ -165,12 +165,12 @@ CONFLICT
|
|||
|
||||
it 'raises UnexpectedDelimiter when it is followed by another middle delimiter' do
|
||||
expect { parse_text(start_text + '=======' + end_text) }
|
||||
.to raise_error(Gitlab::Git::ConflictParser::UnexpectedDelimiter)
|
||||
.to raise_error(Gitlab::Git::Conflict::Parser::UnexpectedDelimiter)
|
||||
end
|
||||
|
||||
it 'raises UnexpectedDelimiter when it is followed by a start delimiter' do
|
||||
expect { parse_text(start_text + start_text + end_text) }
|
||||
.to raise_error(Gitlab::Git::ConflictParser::UnexpectedDelimiter)
|
||||
.to raise_error(Gitlab::Git::Conflict::Parser::UnexpectedDelimiter)
|
||||
end
|
||||
|
||||
it 'does not raise when it is followed by a start delimiter for another path' do
|
||||
|
@ -183,25 +183,25 @@ CONFLICT
|
|||
start_text = "<<<<<<< README.md\n=======\n"
|
||||
|
||||
expect { parse_text(start_text) }
|
||||
.to raise_error(Gitlab::Git::ConflictParser::MissingEndDelimiter)
|
||||
.to raise_error(Gitlab::Git::Conflict::Parser::MissingEndDelimiter)
|
||||
|
||||
expect { parse_text(start_text + '>>>>>>> some-other-path.md') }
|
||||
.to raise_error(Gitlab::Git::ConflictParser::MissingEndDelimiter)
|
||||
.to raise_error(Gitlab::Git::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::Git::ConflictParser::UnmergeableFile)
|
||||
.to raise_error(Gitlab::Git::Conflict::Parser::UnmergeableFile)
|
||||
|
||||
expect { parse_text(nil) }
|
||||
.to raise_error(Gitlab::Git::ConflictParser::UnmergeableFile)
|
||||
.to raise_error(Gitlab::Git::Conflict::Parser::UnmergeableFile)
|
||||
end
|
||||
|
||||
it 'raises UnmergeableFile when the file is over 200 KB' do
|
||||
expect { parse_text('a' * 204801) }
|
||||
.to raise_error(Gitlab::Git::ConflictParser::UnmergeableFile)
|
||||
.to raise_error(Gitlab::Git::Conflict::Parser::UnmergeableFile)
|
||||
end
|
||||
|
||||
# All text from Rugged has an encoding of ASCII_8BIT, so force that in
|
||||
|
@ -216,7 +216,7 @@ CONFLICT
|
|||
context 'when the file contains non-UTF-8 characters' do
|
||||
it 'raises UnsupportedEncoding' do
|
||||
expect { parse_text("a\xC4\xFC".force_encoding(Encoding::ASCII_8BIT)) }
|
||||
.to raise_error(Gitlab::Git::ConflictParser::UnsupportedEncoding)
|
||||
.to raise_error(Gitlab::Git::Conflict::Parser::UnsupportedEncoding)
|
||||
end
|
||||
end
|
||||
end
|
|
@ -105,7 +105,7 @@ describe DiffNote do
|
|||
|
||||
describe "#line_code" do
|
||||
it "returns the correct line code" do
|
||||
line_code = Gitlab::Git::DiffLineCode.generate(position.file_path, position.formatter.new_line, 15)
|
||||
line_code = Gitlab::Git::Conflict::LineCode.generate(position.file_path, position.formatter.new_line, 15)
|
||||
|
||||
expect(subject.line_code).to eq(line_code)
|
||||
end
|
||||
|
|
|
@ -204,16 +204,16 @@ describe MergeRequests::Conflicts::ResolveService do
|
|||
|
||||
it 'raises a ResolutionError error' do
|
||||
expect { service.execute(user, invalid_params) }
|
||||
.to raise_error(Gitlab::Git::Merge::ResolutionError)
|
||||
.to raise_error(Gitlab::Git::Conflict::Resolver::ResolutionError)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the content of a file is unchanged' do
|
||||
let(:merge) do
|
||||
MergeRequests::Conflicts::ListService.new(merge_request).conflicts.merge
|
||||
let(:resolver) do
|
||||
MergeRequests::Conflicts::ListService.new(merge_request).conflicts.resolver
|
||||
end
|
||||
let(:regex_conflict) do
|
||||
merge.conflict_for_path('files/ruby/regex.rb', 'files/ruby/regex.rb')
|
||||
resolver.conflict_for_path('files/ruby/regex.rb', 'files/ruby/regex.rb')
|
||||
end
|
||||
|
||||
let(:invalid_params) do
|
||||
|
@ -235,7 +235,7 @@ describe MergeRequests::Conflicts::ResolveService do
|
|||
|
||||
it 'raises a ResolutionError error' do
|
||||
expect { service.execute(user, invalid_params) }
|
||||
.to raise_error(Gitlab::Git::Merge::ResolutionError)
|
||||
.to raise_error(Gitlab::Git::Conflict::Resolver::ResolutionError)
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -255,7 +255,7 @@ describe MergeRequests::Conflicts::ResolveService do
|
|||
|
||||
it 'raises a ResolutionError error' do
|
||||
expect { service.execute(user, invalid_params) }
|
||||
.to raise_error(Gitlab::Git::Merge::ResolutionError)
|
||||
.to raise_error(Gitlab::Git::Conflict::Resolver::ResolutionError)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
Loading…
Reference in a new issue