use crate::replay_patch::{PatchHunk, split_keeping_newlines};
#[derive(Clone, Debug, Eq, PartialEq)]
struct ImageLine {
text: Vec<u8>,
patched: bool,
}
fn hash_key(text: &[u8]) -> Vec<u8> {
return text
.iter()
.copied()
.filter(|byte| { return !matches!(*byte, b' ' | b'\t' | b'\n' | 0x0b | 0x0c | b'\r'); })
.collect();
}
fn line_matches(line: &ImageLine, wanted: &[u8], incomplete_last: bool, match_end: bool) -> bool {
if line.patched {
return false;
}
if !incomplete_last || match_end {
return line.text == wanted;
}
return line.text.starts_with(wanted) && hash_key(&line.text) == hash_key(wanted);
}
fn matches_at(
image: &[ImageLine],
preimage: &[Vec<u8>],
at: usize,
match_beginning: bool,
match_end: bool,
) -> bool {
let end: usize = preimage.len() + at;
if end > image.len() || (match_end && end != image.len()) || (match_beginning && at != 0) {
return false;
}
return preimage.iter().enumerate().all(|(index, wanted)| {
let incomplete_last: bool = index + 1 == preimage.len() && !wanted.ends_with(b"\n");
return line_matches(&image[at + index], wanted, incomplete_last, match_end);
});
}
fn find_position(
image: &[ImageLine],
preimage: &[Vec<u8>],
line: i64,
match_beginning: bool,
match_end: bool,
) -> Option<usize> {
let length: i64 = i64::try_from(image.len()).unwrap_or(i64::MAX);
let preimage_length: i64 = i64::try_from(preimage.len()).unwrap_or(i64::MAX);
let anchored: i64 = if match_beginning {
0
} else if match_end {
length - preimage_length
} else {
line
};
let first: i64 = anchored.max(0).min(length);
let matches = |at: i64| -> Option<usize> {
let index: usize = usize::try_from(at).ok()?;
return matches_at(image, preimage, index, match_beginning, match_end).then_some(index);
};
if let Some(found) = matches(first) {
return Some(found);
}
let mut distance: i64 = 1;
while first + distance <= length || first - distance >= 0 {
if let Some(found) = matches(first + distance) {
return Some(found);
}
if let Some(found) = matches(first - distance) {
return Some(found);
}
distance += 1;
}
return None;
}
fn reverse_hunk(image: &[ImageLine], hunk: &PatchHunk) -> Option<Vec<ImageLine>> {
let preimage: Vec<Vec<u8>> = hunk
.lines
.iter()
.filter(|line| { return line.op != b'-'; })
.map(|line| { return line.text.clone(); })
.collect();
let postimage: Vec<ImageLine> = hunk
.lines
.iter()
.filter(|line| { return line.op != b'+'; })
.map(|line| { return ImageLine {
text: line.text.clone(),
patched: true,
}; })
.collect();
let trailing: usize = match hunk.lines.iter().rposition(|line| { return line.op != b' '; }) {
Some(last_change) => hunk.lines.len() - 1 - last_change,
None => hunk.lines.len(),
};
let line: i64 = if hunk.old_start == 0 { 0 } else { hunk.old_start - 1 };
let at: usize = find_position(
image,
&preimage,
line,
hunk.new_start <= 1,
trailing == 0,
)?;
let mut rebuilt: Vec<ImageLine> = image[..at].to_vec();
rebuilt.extend(postimage);
rebuilt.extend_from_slice(&image[at + preimage.len()..]);
return Some(rebuilt);
}
pub fn reverse_applies(hunks: &[PatchHunk], prepared: &[u8]) -> bool {
let mut image: Vec<ImageLine> = split_keeping_newlines(prepared)
.into_iter()
.map(|text| { return ImageLine {
text,
patched: false,
}; })
.collect();
for hunk in hunks {
match reverse_hunk(&image, hunk) {
Some(next) => image = next,
None => return false,
}
}
return true;
}
#[cfg(test)]
#[path = "replay_reverse_apply_test_support.rs"]
mod test_support_batch;
#[cfg(test)]
#[path = "replay_reverse_apply_tests.rs"]
mod tests;