Commit e9e06ca6 authored by Douwe Maan's avatar Douwe Maan

Add Gitlab::Diff::LineMapper

parent 9abcc0a9
# 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 line number based on new 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.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
require 'spec_helper'
describe Gitlab::Diff::LineMapper, lib: true do
include RepoHelpers
let(:project) { create(:project) }
let(:repository) { project.repository }
let(:commit) { project.commit(sample_commit.id) }
let(:diffs) { commit.diffs }
let(:diff) { diffs.first }
let(:diff_file) { Gitlab::Diff::File.new(diff, diff_refs: commit.diff_refs, repository: repository) }
subject { described_class.new(diff_file) }
describe '#old_to_new' do
context "with a diff file" do
let(:mapping) do
{
1 => 1,
2 => 2,
3 => 3,
4 => 4,
5 => 5,
6 => 6,
7 => 7,
8 => 8,
9 => nil,
# nil => 9,
10 => 10,
11 => 11,
12 => 12,
13 => nil,
14 => nil,
# nil => 15,
# nil => 16,
# nil => 17,
# nil => 18,
# nil => 19,
# nil => 20,
15 => 21,
16 => 22,
17 => 23,
18 => 24,
19 => 25,
20 => 26,
21 => 27,
# nil => 28,
22 => 29,
23 => 30,
24 => 31,
25 => 32,
26 => 33,
27 => 34,
28 => 35,
29 => 36,
30 => 37
}
end
it 'returns the new line number for the old line number' do
mapping.each do |old_line, new_line|
expect(subject.old_to_new(old_line)).to eq(new_line)
end
end
end
context "without a diff file" do
let(:diff_file) { nil }
it "returns the same line number" do
expect(subject.old_to_new(100)).to eq(100)
end
end
end
describe '#new_to_old' do
context "with a diff file" do
let(:mapping) do
{
1 => 1,
2 => 2,
3 => 3,
4 => 4,
5 => 5,
6 => 6,
7 => 7,
8 => 8,
# nil => 9,
9 => nil,
10 => 10,
11 => 11,
12 => 12,
# nil => 13,
# nil => 14,
13 => nil,
14 => nil,
15 => nil,
16 => nil,
17 => nil,
18 => nil,
19 => nil,
20 => nil,
21 => 15,
22 => 16,
23 => 17,
24 => 18,
25 => 19,
26 => 20,
27 => 21,
28 => nil,
29 => 22,
30 => 23,
31 => 24,
32 => 25,
33 => 26,
34 => 27,
35 => 28,
36 => 29,
37 => 30
}
end
it 'returns the old line number for the new line number' do
mapping.each do |new_line, old_line|
expect(subject.new_to_old(new_line)).to eq(old_line)
end
end
end
context "without a diff file" do
let(:diff_file) { nil }
it "returns the same line number" do
expect(subject.new_to_old(100)).to eq(100)
end
end
end
end
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment