BigW Consortium Gitlab

line_mapper.rb 2.19 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
# When provided a diff for a specific file, maps old line numbers to new line
# numbers and back, to find out where a specific line in a file was moved by the
# changes.
module Gitlab
  module Diff
    class LineMapper
      attr_accessor :diff_file

      def initialize(diff_file)
        @diff_file = diff_file
      end

      # Find new line number for old line number.
      def old_to_new(old_line)
        map_line_number(old_line, from: :old_line, to: :new_line)
      end

      # Find old line number for new line number.
      def new_to_old(new_line)
        map_line_number(new_line, from: :new_line, to: :old_line)
      end

      private

      def diff_lines
        @diff_lines ||= @diff_file.diff_lines
      end

29
      # Find old/new line number based on its old/new counterpart line number.
30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64
      def map_line_number(from_line, from:, to:)
        # If no diff file could be found, the file wasn't changed, and the
        # mapped line number is the same as the specified line number.
        return from_line unless diff_file

        # To find the mapped line number for the specified line number,
        # we need to find:
        # - The diff line with that exact line number, if it is in the diff context
        # - The first diff line with a higher line number, if it falls between diff contexts
        # - The last known diff line, if it falls after the last diff context
        diff_line = diff_lines.find do |diff_line|
          diff_from_line = diff_line.send(from)
          diff_from_line && diff_from_line >= from_line
        end
        diff_line ||= diff_lines.last

        # If no diff line could be found, the file wasn't changed, and the
        # mapped line number is the same as the specified line number.
        return from_line unless diff_line

        diff_from_line = diff_line.send(from)
        diff_to_line = diff_line.send(to)

        # If the line was removed, there is no mapped line number.
        return unless diff_to_line

        # Because we may not have the diff line with the exact line number
        # we were looking for, we need to adjust the mapped line number.
        distance = diff_from_line - from_line

        diff_to_line - distance
      end
    end
  end
end