Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
128 changes: 113 additions & 15 deletions src/context_diff.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,54 @@ impl Mismatch {
}
}

/// A run of changed lines with no common line between them.
struct ChangeBlock {
expected_start: usize,
actual_start: usize,
deletes: bool,
inserts: bool,
}

impl ChangeBlock {
fn new(mismatch: &Mismatch) -> Self {
Self {
expected_start: mismatch.expected.len(),
actual_start: mismatch.actual.len(),
deletes: false,
inserts: false,
}
}

/// Opens a block at the end of `mismatch` if none is open, and records
/// whether it deletes or inserts lines.
fn mark(block: &mut Option<Self>, mismatch: &Mismatch, deletes: bool, inserts: bool) {
let block = block.get_or_insert_with(|| Self::new(mismatch));
block.deletes |= deletes;
block.inserts |= inserts;
}

fn close(block: &mut Option<Self>, mismatch: &mut Mismatch) {
if let Some(block) = block.take() {
block.finish(mismatch);
}
}

/// Like GNU diff, a block that both deletes and inserts lines marks all of
/// them as changed (`!`), not only as many as pair up.
fn finish(self, mismatch: &mut Mismatch) {
if !(self.deletes && self.inserts) {
return;
}
let expected = mismatch.expected[self.expected_start..].iter_mut();
let actual = mismatch.actual[self.actual_start..].iter_mut();
for line in expected.chain(actual) {
if let DiffLine::Add(content) = line {
*line = DiffLine::Change(std::mem::take(content));
}
}
}
}

// Produces a diff between the expected output and actual output.
fn make_diff(
expected: &[u8],
Expand Down Expand Up @@ -77,8 +125,7 @@ fn make_diff(
actual_lines.pop();
}

// Rust only allows allocations to grow to isize::MAX, and this is bigger than that.
let mut expected_lines_change_idx: usize = !0;
let mut block: Option<ChangeBlock> = None;

for result in diff::slice(&expected_lines, &actual_lines) {
match result {
Expand All @@ -96,7 +143,7 @@ fn make_diff(
mismatch.actual.push(DiffLine::Context(line.to_vec()));
}

expected_lines_change_idx = mismatch.expected.len();
ChangeBlock::mark(&mut block, &mismatch, true, false);
mismatch.expected.push(DiffLine::Add(str.to_vec()));
if line_number_expected > expected_lines_count {
mismatch.expected_missing_nl = true;
Expand All @@ -111,36 +158,27 @@ fn make_diff(
line_number_expected - context_queue.len(),
line_number_actual - context_queue.len(),
);
expected_lines_change_idx = !0;
}

while let Some(line) = context_queue.pop_front() {
mismatch.expected.push(DiffLine::Context(line.to_vec()));
mismatch.actual.push(DiffLine::Context(line.to_vec()));
}

if let Some(DiffLine::Add(content)) =
mismatch.expected.get_mut(expected_lines_change_idx)
{
let content = std::mem::take(content);
mismatch.expected[expected_lines_change_idx] = DiffLine::Change(content);
expected_lines_change_idx = expected_lines_change_idx.wrapping_sub(1); // if 0, becomes !0
mismatch.actual.push(DiffLine::Change(str.to_vec()));
} else {
mismatch.actual.push(DiffLine::Add(str.to_vec()));
}
ChangeBlock::mark(&mut block, &mismatch, false, true);
mismatch.actual.push(DiffLine::Add(str.to_vec()));
if line_number_actual > actual_lines_count {
mismatch.actual_missing_nl = true;
}
line_number_actual += 1;
lines_since_mismatch = 0;
}
diff::Result::Both(str, _) => {
expected_lines_change_idx = !0;
// if one of them is missing a newline and the other isn't, then they don't actually match
if (line_number_actual > actual_lines_count)
&& (line_number_expected > expected_lines_count)
{
ChangeBlock::close(&mut block, &mut mismatch);
if context_queue.len() < context_size {
while let Some(line) = context_queue.pop_front() {
mismatch.expected.push(DiffLine::Context(line.to_vec()));
Expand All @@ -166,6 +204,7 @@ fn make_diff(
mismatch.expected.push(DiffLine::Context(line.to_vec()));
mismatch.actual.push(DiffLine::Context(line.to_vec()));
}
ChangeBlock::mark(&mut block, &mismatch, true, true);
mismatch.expected.push(DiffLine::Change(str.to_vec()));
mismatch.actual.push(DiffLine::Change(str.to_vec()));
mismatch.actual_missing_nl = true;
Expand All @@ -182,11 +221,13 @@ fn make_diff(
mismatch.expected.push(DiffLine::Context(line.to_vec()));
mismatch.actual.push(DiffLine::Context(line.to_vec()));
}
ChangeBlock::mark(&mut block, &mismatch, true, true);
mismatch.expected.push(DiffLine::Change(str.to_vec()));
mismatch.expected_missing_nl = true;
mismatch.actual.push(DiffLine::Change(str.to_vec()));
lines_since_mismatch = 0;
} else {
ChangeBlock::close(&mut block, &mut mismatch);
debug_assert!(context_queue.len() <= context_size);
if context_queue.len() >= context_size {
let _ = context_queue.pop_front();
Expand All @@ -209,6 +250,9 @@ fn make_diff(
}
}

if let Some(block) = block {
block.finish(&mut mismatch);
}
results.push(mismatch);
results.remove(0);

Expand Down Expand Up @@ -804,6 +848,60 @@ mod tests {
assert!(nodiff_brief.is_empty());
}

#[test]
fn test_change_block_lines_all_changed() {
use crate::assert_diff_eq;

let params = Params {
from: "foo".into(),
to: "bar".into(),
..Default::default()
};

// Two lines replaced by one: both old lines are changed, not one deleted.
let from = ["a", "b", "c", "d", ""].join("\n");
let to = ["a", "x", "d", ""].join("\n");
let expected = [
"*** foo\tTIMESTAMP",
"--- bar\tTIMESTAMP",
"***************",
"*** 1,4 ****",
" a",
"! b",
"! c",
" d",
"--- 1,3 ----",
" a",
"! x",
" d",
"",
]
.join("\n");
let output = diff(from.as_bytes(), to.as_bytes(), &params);
assert_diff_eq!(output, expected);

// One line replaced by three: all three new lines are changed.
let from = ["a", "b", ""].join("\n");
let to = ["a", "x", "y", "z", ""].join("\n");
let expected = [
"*** foo\tTIMESTAMP",
"--- bar\tTIMESTAMP",
"***************",
"*** 1,2 ****",
" a",
"! b",
"--- 1,4 ----",
" a",
"! x",
"! y",
"! z",
"",
]
.join("\n");
let output = diff(from.as_bytes(), to.as_bytes(), &params);
assert_diff_eq!(output, expected);
}

#[test]
fn test_hunk_merging() {
use crate::assert_diff_eq;
Expand Down
Loading