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