1# frozen_string_literal: true 2 3module Gitlab 4 module Git 5 module Conflict 6 class File 7 UnsupportedEncoding = Class.new(StandardError) 8 9 attr_reader :ancestor_path, :their_path, :our_path, :our_mode, :repository, :commit_oid 10 11 attr_accessor :raw_content 12 13 def initialize(repository, commit_oid, conflict, raw_content) 14 @repository = repository 15 @commit_oid = commit_oid 16 @ancestor_path = conflict[:ancestor][:path] 17 @their_path = conflict[:theirs][:path] 18 @our_path = conflict[:ours][:path] 19 @our_mode = conflict[:ours][:mode] 20 @raw_content = raw_content 21 end 22 23 def lines 24 return @lines if defined?(@lines) 25 26 begin 27 @type = 'text' 28 @lines = Gitlab::Git::Conflict::Parser.parse(content, 29 our_path: our_path, 30 their_path: their_path) 31 rescue Gitlab::Git::Conflict::Parser::ParserError 32 @type = 'text-editor' 33 @lines = nil 34 end 35 end 36 37 def content 38 @content ||= @raw_content.dup.force_encoding('UTF-8') 39 40 raise UnsupportedEncoding unless @content.valid_encoding? 41 42 @content 43 end 44 45 def type 46 lines unless @type 47 48 @type.inquiry 49 end 50 51 def our_blob 52 # REFACTOR NOTE: the source of `commit_oid` used to be 53 # `merge_request.diff_refs.head_sha`. Instead of passing this value 54 # around the new lib structure, I decided to use `@commit_oid` which is 55 # equivalent to `merge_request.source_branch_head.raw.rugged_commit.oid`. 56 # That is what `merge_request.diff_refs.head_sha` is equivalent to when 57 # `merge_request` is not persisted (see `MergeRequest#diff_head_commit`). 58 # I think using the same oid is more consistent anyways, but if Conflicts 59 # start breaking, the change described above is a good place to look at. 60 @our_blob ||= repository.blob_at(@commit_oid, our_path) 61 end 62 63 def line_code(line) 64 Gitlab::Git.diff_line_code(our_path, line[:line_new], line[:line_old]) 65 end 66 67 def resolve_lines(resolution) 68 section_id = nil 69 70 lines.map do |line| 71 unless line[:type] 72 section_id = nil 73 next line 74 end 75 76 section_id ||= line_code(line) 77 78 case resolution[section_id] 79 when 'head' 80 next unless line[:type] == 'new' 81 when 'origin' 82 next unless line[:type] == 'old' 83 else 84 raise Gitlab::Git::Conflict::Resolver::ResolutionError, "Missing resolution for section ID: #{section_id}" 85 end 86 87 line 88 end.compact 89 end 90 91 def resolve_content(resolution) 92 if resolution == content 93 raise Gitlab::Git::Conflict::Resolver::ResolutionError, "Resolved content has no changes for file #{our_path}" 94 end 95 96 resolution 97 end 98 99 def path 100 # There are conflict scenarios (e.g. file is removed on source) wherein 101 # our_path will be blank/nil. Since we are indexing them by path in 102 # `#conflicts` helper and we want to match the diff file to a conflict 103 # in `DiffFileEntity#highlighted_diff_lines`, we need to fallback to 104 # their_path (this is the path on target). 105 our_path.presence || their_path 106 end 107 end 108 end 109 end 110end 111