Commit 5fea640e authored by Douwe Maan's avatar Douwe Maan

Merge branch 'multi-line-inline-diff' into 'master'

Render inline diffs for multiple changed lines following eachother

Before:

![Screen_Shot_2016-07-11_at_00.08.27](/uploads/b14664211e0f5cef6e77a78eadfcbcdf/Screen_Shot_2016-07-11_at_00.08.27.png)

After:

![Screen_Shot_2016-07-11_at_00.07.34](/uploads/567be631869a4867a2edf6ff7eda6369/Screen_Shot_2016-07-11_at_00.07.34.png)

See merge request !5174
parents 67668d9d 489e1937
...@@ -35,6 +35,7 @@ v 8.10.0 (unreleased) ...@@ -35,6 +35,7 @@ v 8.10.0 (unreleased)
- Fix changing issue state columns in milestone view - Fix changing issue state columns in milestone view
- Update health_check gem to version 2.1.0 - Update health_check gem to version 2.1.0
- Add notification settings dropdown for groups - Add notification settings dropdown for groups
- Render inline diffs for multiple changed lines following eachother
- Wildcards for protected branches. !4665 - Wildcards for protected branches. !4665
- Allow importing from Github using Personal Access Tokens. (Eric K Idema) - Allow importing from Github using Personal Access Tokens. (Eric K Idema)
- API: Todos !3188 (Robert Schilling) - API: Todos !3188 (Robert Schilling)
......
module Gitlab module Gitlab
module Diff module Diff
class InlineDiff class InlineDiff
# Regex to find a run of deleted lines followed by the same number of added lines
LINE_PAIRS_PATTERN = %r{
# Runs start at the beginning of the string (the first line) or after a space (for an unchanged line)
(?:\A|\s)
# This matches a number of `-`s followed by the same number of `+`s through recursion
(?<del_ins>
-
\g<del_ins>?
\+
)
# Runs end at the end of the string (the last line) or before a space (for an unchanged line)
(?=\s|\z)
}x.freeze
attr_accessor :old_line, :new_line, :offset attr_accessor :old_line, :new_line, :offset
def self.for_lines(lines) def self.for_lines(lines)
local_edit_indexes = self.find_local_edits(lines) changed_line_pairs = self.find_changed_line_pairs(lines)
inline_diffs = [] inline_diffs = []
local_edit_indexes.each do |index| changed_line_pairs.each do |old_index, new_index|
old_index = index
new_index = index + 1
old_line = lines[old_index] old_line = lines[old_index]
new_line = lines[new_index] new_line = lines[new_index]
...@@ -51,18 +65,28 @@ module Gitlab ...@@ -51,18 +65,28 @@ module Gitlab
private private
def self.find_local_edits(lines) # Finds pairs of old/new line pairs that represent the same line that changed
line_prefixes = lines.map { |line| line.match(/\A([+-])/) ? $1 : ' ' } def self.find_changed_line_pairs(lines)
joined_line_prefixes = " #{line_prefixes.join} " # Prefixes of all diff lines, indicating their types
# For example: `" - + -+ ---+++ --+ -++"`
offset = 0 line_prefixes = lines.each_with_object("") { |line, s| s << line[0] }.gsub(/[^ +-]/, ' ')
local_edit_indexes = []
while index = joined_line_prefixes.index(" -+ ", offset) changed_line_pairs = []
local_edit_indexes << index line_prefixes.scan(LINE_PAIRS_PATTERN) do
offset = index + 1 # For `"---+++"`, `begin_index == 0`, `end_index == 6`
begin_index, end_index = Regexp.last_match.offset(:del_ins)
# For `"---+++"`, `changed_line_count == 3`
changed_line_count = (end_index - begin_index) / 2
halfway_index = begin_index + changed_line_count
(begin_index...halfway_index).each do |i|
# For `"---+++"`, index 1 maps to 1 + 3 = 4
changed_line_pairs << [i, i + changed_line_count]
end
end end
local_edit_indexes changed_line_pairs
end end
def longest_common_prefix(a, b) def longest_common_prefix(a, b)
......
...@@ -3,14 +3,19 @@ require 'spec_helper' ...@@ -3,14 +3,19 @@ require 'spec_helper'
describe Gitlab::Diff::InlineDiff, lib: true do describe Gitlab::Diff::InlineDiff, lib: true do
describe '.for_lines' do describe '.for_lines' do
let(:diff) do let(:diff) do
<<eos <<-EOF.strip_heredoc
class Test class Test
- def initialize(test = true) - def initialize(test = true)
+ def initialize(test = false) + def initialize(test = false)
@test = test @test = test
end - if true
end - @foo = "bar"
eos + unless false
+ @foo = "baz"
end
end
end
EOF
end end
let(:subject) { described_class.for_lines(diff.lines) } let(:subject) { described_class.for_lines(diff.lines) }
...@@ -20,8 +25,11 @@ eos ...@@ -20,8 +25,11 @@ eos
expect(subject[1]).to eq([25..27]) expect(subject[1]).to eq([25..27])
expect(subject[2]).to eq([25..28]) expect(subject[2]).to eq([25..28])
expect(subject[3]).to be_nil expect(subject[3]).to be_nil
expect(subject[4]).to be_nil expect(subject[4]).to eq([5..10])
expect(subject[5]).to be_nil expect(subject[5]).to eq([17..17])
expect(subject[6]).to eq([5..15])
expect(subject[7]).to eq([17..17])
expect(subject[8]).to be_nil
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