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
 |