class LineTracker
Public Instance Methods
track_mutations!(file_diffs)
click to toggle source
# File lib/line_tracker.rb, line 4 def track_mutations!(file_diffs) deletions = index_deletions(file_diffs) file_deletions = deletions.delete(:files) grouped_file_diffs(file_diffs).reduce(change_context()) do |acc, (file_name, file_diff)| file_diff[:diffs].each do |diff| file_deletes = (file_deletions[file_diff[:a_file_name]] || {}) diff.insertions.each_with_index do |insertion, i| insertion = insertion.strip() next if Line.meaningless?(insertion) line_number = diff.insert_start + i - 1 if movement?(deletions, insertion) index!(acc[:movements], file_name, line_number, deletions[insertion].shift()) else changed_line = find_change(file_deletes, insertion) index!(acc[:changes], file_name, line_number, changed_line) if changed_line end end end acc end end
Private Instance Methods
change_context()
click to toggle source
# File lib/line_tracker.rb, line 47 def change_context() { movements: {}, changes: {} } end
find_change(deletions, new_line)
click to toggle source
# File lib/line_tracker.rb, line 35 def find_change(deletions, new_line) change = (deletions || {}) .keys .detect { |deletion| Line::similar?(deletion, new_line) } return change ? deletions[change].shift() : nil end
grouped_file_diffs(file_diffs)
click to toggle source
# File lib/line_tracker.rb, line 83 def grouped_file_diffs(file_diffs) file_diffs.reduce({}) do |acc, (file_name, file_diff)| acc[file_diff.b_file_name] ||= {diffs: [], a_file_name: file_diff.a_file_name} acc[file_diff.b_file_name][:diffs].concat(file_diff.diffs) acc end end
index!(acc, file_name, line_number, old_line)
click to toggle source
# File lib/line_tracker.rb, line 42 def index!(acc, file_name, line_number, old_line) acc[file_name] ||= {} acc[file_name][line_number] = old_line end
index_deletions(file_diffs)
click to toggle source
TODO:
1: Machine learning based match to avoid false positives from other files. a: Within commit, within file, be most lenient. b: Within commit, be somewhat lenient. c: Within entire codebase, be pretty strict. Exclude even exact matches some times. 2: Once a line is matched from the graveyard, remove it -- it's alive again.
# File lib/line_tracker.rb, line 60 def index_deletions(file_diffs) file_diffs .reduce({files: {}, }) do |acc, (file_name, file_diff)| deleted_file = file_diff.a_file_name file_diff.diffs.each do |diff| diff.deletions.each_with_index do |deletion, index| deletion = deletion.strip() next if Line.meaningless?(deletion) line_number = diff.delete_start + index - 1 line = {from: deleted_file, line: line_number} acc[:files][file_name] ||= {} acc[:files][file_name][deletion] ||= [] acc[:files][file_name][deletion] << line acc[deletion] ||= [] acc[deletion] << line end end acc end end
movement?(deletions, new_line)
click to toggle source
# File lib/line_tracker.rb, line 31 def movement?(deletions, new_line) (deletions[new_line] || []).count > 0 end