1# frozen_string_literal: true 2module DiffPositionableNote 3 extend ActiveSupport::Concern 4 5 included do 6 before_validation :set_original_position, on: :create 7 before_validation :update_position, on: :create, if: :on_text?, unless: :importing? 8 9 serialize :original_position, Gitlab::Diff::Position # rubocop:disable Cop/ActiveRecordSerialize 10 serialize :position, Gitlab::Diff::Position # rubocop:disable Cop/ActiveRecordSerialize 11 serialize :change_position, Gitlab::Diff::Position # rubocop:disable Cop/ActiveRecordSerialize 12 13 validate :diff_refs_match_commit, if: :for_commit? 14 validates :position, json_schema: { filename: "position", hash_conversion: true } 15 end 16 17 %i(original_position position change_position).each do |meth| 18 define_method "#{meth}=" do |new_position| 19 if new_position.is_a?(String) 20 new_position = Gitlab::Json.parse(new_position) rescue nil 21 end 22 23 if new_position.is_a?(Hash) 24 new_position = new_position.with_indifferent_access 25 new_position = Gitlab::Diff::Position.new(new_position) 26 elsif !new_position.is_a?(Gitlab::Diff::Position) 27 new_position = nil 28 end 29 30 return if new_position == read_attribute(meth) 31 32 super(new_position) 33 end 34 end 35 36 def on_text? 37 !!position&.on_text? 38 end 39 40 def on_image? 41 !!position&.on_image? 42 end 43 44 def supported? 45 for_commit? || self.noteable.has_complete_diff_refs? 46 end 47 48 def active?(diff_refs = nil) 49 return false unless supported? 50 return true if for_commit? 51 52 diff_refs ||= noteable.diff_refs 53 54 self.position.diff_refs == diff_refs 55 end 56 57 def set_original_position 58 return unless position 59 60 self.original_position = self.position.dup unless self.original_position&.complete? 61 end 62 63 def update_position 64 return unless supported? 65 return if for_commit? 66 67 return if active? 68 return unless position 69 70 tracer = Gitlab::Diff::PositionTracer.new( 71 project: self.project, 72 old_diff_refs: self.position.diff_refs, 73 new_diff_refs: self.noteable.diff_refs, 74 paths: self.position.paths 75 ) 76 77 result = tracer.trace(self.position) 78 return unless result 79 80 if result[:outdated] 81 self.change_position = result[:position] 82 else 83 self.position = result[:position] 84 end 85 end 86 87 def diff_refs_match_commit 88 return if self.original_position.diff_refs == commit&.diff_refs 89 90 errors.add(:commit_id, 'does not match the diff refs') 91 end 92end 93