Skip to content

Commit ff76e5d

Browse files
committed
fix(compact): normalize stale diff op cursors
1 parent db010ec commit ff76e5d

3 files changed

Lines changed: 76 additions & 0 deletions

File tree

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,10 @@ All notable changes to similar are documented here.
44

55
## Unreleased
66

7+
* Fixed `Compact` emitting inconsistent `DiffOp` cursor positions after
8+
compaction, which could leave `Delete`/`Insert` operations with stale
9+
`new_index`/`old_index` values.
10+
711
## 3.0.0
812

913
* Added a Git-style Histogram diff implementation exposed as

src/algorithms/compact.rs

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,56 @@ where
142142
}
143143
pointer += 1;
144144
}
145+
146+
normalize_diff_op_cursors(ops);
147+
}
148+
149+
fn normalize_diff_op_cursors(ops: &mut [DiffOp]) {
150+
let mut old_cursor = 0;
151+
let mut new_cursor = 0;
152+
153+
if let Some(op) = ops.first() {
154+
old_cursor = op.old_range().start;
155+
new_cursor = op.new_range().start;
156+
}
157+
158+
for op in ops.iter_mut() {
159+
match op {
160+
DiffOp::Equal {
161+
old_index,
162+
new_index,
163+
len,
164+
} => {
165+
old_cursor = *old_index + *len;
166+
new_cursor = *new_index + *len;
167+
}
168+
DiffOp::Delete {
169+
old_index,
170+
old_len,
171+
new_index,
172+
} => {
173+
*new_index = new_cursor;
174+
old_cursor = *old_index + *old_len;
175+
}
176+
DiffOp::Insert {
177+
old_index,
178+
new_index,
179+
new_len,
180+
} => {
181+
*old_index = old_cursor;
182+
new_cursor = *new_index + *new_len;
183+
}
184+
DiffOp::Replace {
185+
old_index,
186+
old_len,
187+
new_index,
188+
new_len,
189+
} => {
190+
old_cursor = *old_index + *old_len;
191+
new_cursor = *new_index + *new_len;
192+
}
193+
}
194+
}
145195
}
146196

147197
fn shift_diff_ops_up<Old, New>(

src/common.rs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,28 @@ fn test_non_string_iter_change() {
190190
);
191191
}
192192

193+
#[test]
194+
fn test_compact_keeps_diffop_cursors_contiguous() {
195+
let a = vec![1, 1, 0, 2];
196+
let b = vec![2, 1, 0, 0, 0, 0];
197+
let ops = capture_diff_slices(Algorithm::Myers, &a, &b);
198+
199+
for i in 1..ops.len() {
200+
let prev = &ops[i - 1];
201+
let op = &ops[i];
202+
assert_eq!(
203+
op.old_range().start,
204+
prev.old_range().end,
205+
"old_index gap at op {i}: {prev:?} -> {op:?}"
206+
);
207+
assert_eq!(
208+
op.new_range().start,
209+
prev.new_range().end,
210+
"new_index gap at op {i}: {prev:?} -> {op:?}"
211+
);
212+
}
213+
}
214+
193215
#[test]
194216
fn test_myers_compacts_adjacent_deletes_issue_80() {
195217
let a: Vec<u8> = vec![0, 1, 0, 0, 0, 1, 2];

0 commit comments

Comments
 (0)