Commit e26f7f1a authored by Bob Van Landuyt's avatar Bob Van Landuyt

Merge branch '16950_improve_highlighting_for_diffs' into 'master'

Improve highlighting for merge diffs

See merge request gitlab-org/gitlab!52499
parents 009950fb ee2e5751
---
title: Improve highlighting for merge diffs
merge_request: 52499
author:
type: added
---
name: improved_merge_diff_highlighting
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/52499
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/299884
milestone: '13.9'
type: development
group: group::source code
default_enabled: false
...@@ -18,6 +18,33 @@ module Gitlab ...@@ -18,6 +18,33 @@ module Gitlab
@changes @changes
end end
def changed_ranges(offset: 0)
old_diffs = []
new_diffs = []
new_pointer = old_pointer = offset
generate_diff.each do |(action, content)|
content_size = content.size
if action == :equal
new_pointer += content_size
old_pointer += content_size
end
if action == :delete
old_diffs << (old_pointer..(old_pointer + content_size - 1))
old_pointer += content_size
end
if action == :insert
new_diffs << (new_pointer..(new_pointer + content_size - 1))
new_pointer += content_size
end
end
[old_diffs, new_diffs]
end
def to_html def to_html
@changes.map do |op, text| @changes.map do |op, text|
%{<span class="#{html_class_names(op)}">#{ERB::Util.html_escape(text)}</span>} %{<span class="#{html_class_names(op)}">#{ERB::Util.html_escape(text)}</span>}
......
...@@ -3,12 +3,13 @@ ...@@ -3,12 +3,13 @@
module Gitlab module Gitlab
module Diff module Diff
class Highlight class Highlight
attr_reader :diff_file, :diff_lines, :raw_lines, :repository attr_reader :diff_file, :diff_lines, :raw_lines, :repository, :project
delegate :old_path, :new_path, :old_sha, :new_sha, to: :diff_file, prefix: :diff delegate :old_path, :new_path, :old_sha, :new_sha, to: :diff_file, prefix: :diff
def initialize(diff_lines, repository: nil) def initialize(diff_lines, repository: nil)
@repository = repository @repository = repository
@project = repository&.project
if diff_lines.is_a?(Gitlab::Diff::File) if diff_lines.is_a?(Gitlab::Diff::File)
@diff_file = diff_lines @diff_file = diff_lines
...@@ -66,7 +67,7 @@ module Gitlab ...@@ -66,7 +67,7 @@ module Gitlab
end end
def inline_diffs def inline_diffs
@inline_diffs ||= InlineDiff.for_lines(@raw_lines) @inline_diffs ||= InlineDiff.for_lines(@raw_lines, project: project)
end end
def old_lines def old_lines
......
...@@ -8,6 +8,7 @@ module Gitlab ...@@ -8,6 +8,7 @@ module Gitlab
EXPIRATION = 1.week EXPIRATION = 1.week
VERSION = 1 VERSION = 1
NEXT_VERSION = 2
delegate :diffable, to: :@diff_collection delegate :diffable, to: :@diff_collection
delegate :diff_options, to: :@diff_collection delegate :diff_options, to: :@diff_collection
...@@ -69,12 +70,20 @@ module Gitlab ...@@ -69,12 +70,20 @@ module Gitlab
def key def key
strong_memoize(:redis_key) do strong_memoize(:redis_key) do
['highlighted-diff-files', diffable.cache_key, VERSION, diff_options].join(":") ['highlighted-diff-files', diffable.cache_key, version, diff_options].join(":")
end end
end end
private private
def version
if Feature.enabled?(:improved_merge_diff_highlighting, diffable.project)
NEXT_VERSION
else
VERSION
end
end
def set_highlighted_diff_lines(diff_file, content) def set_highlighted_diff_lines(diff_file, content)
diff_file.highlighted_diff_lines = content.map do |line| diff_file.highlighted_diff_lines = content.map do |line|
Gitlab::Diff::Line.safe_init_from_hash(line) Gitlab::Diff::Line.safe_init_from_hash(line)
......
...@@ -27,28 +27,19 @@ module Gitlab ...@@ -27,28 +27,19 @@ module Gitlab
@offset = offset @offset = offset
end end
def inline_diffs def inline_diffs(project: nil)
# Skip inline diff if empty line was replaced with content # Skip inline diff if empty line was replaced with content
return if old_line == "" return if old_line == ""
lcp = longest_common_prefix(old_line, new_line) if Feature.enabled?(:improved_merge_diff_highlighting, project)
lcs = longest_common_suffix(old_line[lcp..-1], new_line[lcp..-1]) CharDiff.new(old_line, new_line).changed_ranges(offset: offset)
else
lcp += offset deprecated_diff
old_length = old_line.length + offset end
new_length = new_line.length + offset
old_diff_range = lcp..(old_length - lcs - 1)
new_diff_range = lcp..(new_length - lcs - 1)
old_diffs = [old_diff_range] if old_diff_range.begin <= old_diff_range.end
new_diffs = [new_diff_range] if new_diff_range.begin <= new_diff_range.end
[old_diffs, new_diffs]
end end
class << self class << self
def for_lines(lines) def for_lines(lines, project: nil)
changed_line_pairs = find_changed_line_pairs(lines) changed_line_pairs = find_changed_line_pairs(lines)
inline_diffs = [] inline_diffs = []
...@@ -57,7 +48,7 @@ module Gitlab ...@@ -57,7 +48,7 @@ module Gitlab
old_line = lines[old_index] old_line = lines[old_index]
new_line = lines[new_index] new_line = lines[new_index]
old_diffs, new_diffs = new(old_line, new_line, offset: 1).inline_diffs old_diffs, new_diffs = new(old_line, new_line, offset: 1).inline_diffs(project: project)
inline_diffs[old_index] = old_diffs inline_diffs[old_index] = old_diffs
inline_diffs[new_index] = new_diffs inline_diffs[new_index] = new_diffs
...@@ -97,6 +88,24 @@ module Gitlab ...@@ -97,6 +88,24 @@ module Gitlab
private private
# See: https://gitlab.com/gitlab-org/gitlab/-/issues/299884
def deprecated_diff
lcp = longest_common_prefix(old_line, new_line)
lcs = longest_common_suffix(old_line[lcp..-1], new_line[lcp..-1])
lcp += offset
old_length = old_line.length + offset
new_length = new_line.length + offset
old_diff_range = lcp..(old_length - lcs - 1)
new_diff_range = lcp..(new_length - lcs - 1)
old_diffs = [old_diff_range] if old_diff_range.begin <= old_diff_range.end
new_diffs = [new_diff_range] if new_diff_range.begin <= new_diff_range.end
[old_diffs, new_diffs]
end
def longest_common_prefix(a, b) # rubocop:disable Naming/UncommunicativeMethodParamName def longest_common_prefix(a, b) # rubocop:disable Naming/UncommunicativeMethodParamName
max_length = [a.length, b.length].max max_length = [a.length, b.length].max
......
...@@ -169,9 +169,9 @@ RSpec.describe DiffHelper do ...@@ -169,9 +169,9 @@ RSpec.describe DiffHelper do
it "returns strings with marked inline diffs" do it "returns strings with marked inline diffs" do
marked_old_line, marked_new_line = mark_inline_diffs(old_line, new_line) marked_old_line, marked_new_line = mark_inline_diffs(old_line, new_line)
expect(marked_old_line).to eq(%q{abc <span class="idiff left right deletion">&#39;def&#39;</span>}) expect(marked_old_line).to eq(%q{abc <span class="idiff left deletion">&#39;</span>def<span class="idiff right deletion">&#39;</span>})
expect(marked_old_line).to be_html_safe expect(marked_old_line).to be_html_safe
expect(marked_new_line).to eq(%q{abc <span class="idiff left right addition">&quot;def&quot;</span>}) expect(marked_new_line).to eq(%q{abc <span class="idiff left addition">&quot;</span>def<span class="idiff right addition">&quot;</span>})
expect(marked_new_line).to be_html_safe expect(marked_new_line).to be_html_safe
end end
......
...@@ -7,7 +7,7 @@ RSpec.describe Gitlab::Diff::CharDiff do ...@@ -7,7 +7,7 @@ RSpec.describe Gitlab::Diff::CharDiff do
let(:old_string) { "Helo \n Worlld" } let(:old_string) { "Helo \n Worlld" }
let(:new_string) { "Hello \n World" } let(:new_string) { "Hello \n World" }
subject { described_class.new(old_string, new_string) } subject(:diff) { described_class.new(old_string, new_string) }
describe '#generate_diff' do describe '#generate_diff' do
context 'when old string is nil' do context 'when old string is nil' do
...@@ -39,6 +39,28 @@ RSpec.describe Gitlab::Diff::CharDiff do ...@@ -39,6 +39,28 @@ RSpec.describe Gitlab::Diff::CharDiff do
end end
end end
describe '#changed_ranges' do
subject { diff.changed_ranges }
context 'when old string is nil' do
let(:old_string) { nil }
it 'returns lists of changes' do
old_diffs, new_diffs = subject
expect(old_diffs).to eq([])
expect(new_diffs).to eq([0..12])
end
end
it 'returns ranges of changes' do
old_diffs, new_diffs = subject
expect(old_diffs).to eq([11..11])
expect(new_diffs).to eq([3..3])
end
end
describe '#to_html' do describe '#to_html' do
it 'returns an HTML representation of the diff' do it 'returns an HTML representation of the diff' do
subject.generate_diff subject.generate_diff
......
...@@ -233,4 +233,22 @@ RSpec.describe Gitlab::Diff::HighlightCache, :clean_gitlab_redis_cache do ...@@ -233,4 +233,22 @@ RSpec.describe Gitlab::Diff::HighlightCache, :clean_gitlab_redis_cache do
cache.write_if_empty cache.write_if_empty
end end
end end
describe '#key' do
subject { cache.key }
it 'returns the next version of the cache' do
is_expected.to start_with("highlighted-diff-files:#{cache.diffable.cache_key}:2")
end
context 'when feature flag is disabled' do
before do
stub_feature_flags(improved_merge_diff_highlighting: false)
end
it 'returns the original version of the cache' do
is_expected.to start_with("highlighted-diff-files:#{cache.diffable.cache_key}:1")
end
end
end
end end
...@@ -37,6 +37,33 @@ RSpec.describe Gitlab::Diff::InlineDiff do ...@@ -37,6 +37,33 @@ RSpec.describe Gitlab::Diff::InlineDiff do
it 'can handle unchanged empty lines' do it 'can handle unchanged empty lines' do
expect { described_class.for_lines(['- bar', '+ baz', '']) }.not_to raise_error expect { described_class.for_lines(['- bar', '+ baz', '']) }.not_to raise_error
end end
context 'when lines have multiple changes' do
let(:diff) do
<<~EOF
- Hello, how are you?
+ Hi, how are you doing?
EOF
end
let(:subject) { described_class.for_lines(diff.lines) }
it 'finds all inline diffs' do
expect(subject[0]).to eq([3..6])
expect(subject[1]).to eq([3..3, 17..22])
end
context 'when feature flag is disabled' do
before do
stub_feature_flags(improved_merge_diff_highlighting: false)
end
it 'finds all inline diffs' do
expect(subject[0]).to eq([3..19])
expect(subject[1]).to eq([3..22])
end
end
end
end end
describe "#inline_diffs" do describe "#inline_diffs" do
......
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