67 lines
		
	
	
		
			2.4 KiB
		
	
	
	
		
			Ruby
		
	
	
	
			
		
		
	
	
			67 lines
		
	
	
		
			2.4 KiB
		
	
	
	
		
			Ruby
		
	
	
	
# frozen_string_literal: true
 | 
						|
 | 
						|
# 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
 | 
						|
 | 
						|
      # Find old/new line number based on its old/new counterpart line number.
 | 
						|
      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.public_send(from) # rubocop:disable GitlabSecurity/PublicSend
 | 
						|
          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.public_send(from) # rubocop:disable GitlabSecurity/PublicSend
 | 
						|
        diff_to_line = diff_line.public_send(to) # rubocop:disable GitlabSecurity/PublicSend
 | 
						|
 | 
						|
        # 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
 |