diff options
author | tummychow <tummychow@users.noreply.github.com> | 2018-02-19 23:45:17 -0800 |
---|---|---|
committer | tummychow <tummychow@users.noreply.github.com> | 2018-02-20 00:00:59 -0800 |
commit | 951bfce345cebeeeed8cff1f0c0c072347da3f38 (patch) | |
tree | 25a4f8ad92a45e4aec75abdb731420dfe9be435f | |
parent | 45ab83a57d5ffed8f58854c131a132d508341ace (diff) |
fix commutation
-rw-r--r-- | src/commute.rs | 121 | ||||
-rw-r--r-- | src/lib.rs | 41 |
2 files changed, 122 insertions, 40 deletions
diff --git a/src/commute.rs b/src/commute.rs new file mode 100644 index 0000000..eb3c76a --- /dev/null +++ b/src/commute.rs @@ -0,0 +1,121 @@ +extern crate failure; + +use owned; + +/// Returns the unchanged lines around this hunk. +/// +/// Any given hunk has four anchor points: +/// +/// - the last unchanged line before it, on the removed side +/// - the first unchanged line after it, on the removed side +/// - the last unchanged line before it, on the added side +/// - the first unchanged line after it, on the added side +/// +/// This function returns those four line numbers, in that order. +fn anchors(hunk: &owned::Hunk) -> (usize, usize, usize, usize) { + match (hunk.removed.lines.len(), hunk.added.lines.len()) { + (0, 0) => (0, 1, 0, 1), + (removed_len, 0) => ( + hunk.removed.start - 1, + hunk.removed.start + removed_len, + hunk.removed.start - 1, + hunk.removed.start, + ), + (0, added_len) => ( + hunk.added.start - 1, + hunk.added.start, + hunk.added.start - 1, + hunk.added.start + added_len, + ), + (removed_len, added_len) => ( + hunk.removed.start - 1, + hunk.removed.start + removed_len, + hunk.added.start - 1, + hunk.added.start + added_len, + ), + } +} + +fn commute( + first: owned::Hunk, + second: owned::Hunk, +) -> Result<(bool, owned::Hunk, owned::Hunk), failure::Error> { + // represent hunks in content order rather than application order + let (first_above, above, mut below) = match ( + // TODO: skip any comparisons against empty blocks + first.added.start <= second.added.start, + first.removed.start <= second.removed.start, + ) { + (true, true) => (true, first, second), + (false, false) => (false, second, first), + _ => return Err(failure::err_msg("nonsensical hunk ordering")), + }; + + // there has to be at least one unchanged line between the two + // hunks on the first hunk's added side, and the second hunk's + // removed side + let (above_anchor, below_anchor) = if first_above { + (anchors(&above).3, anchors(&below).0) + } else { + (anchors(&above).1, anchors(&below).2) + }; + // the hunks overlap, and cannot commute, so return them in their + // application order + if above_anchor > below_anchor { + return Ok(if first_above { + (false, above, below) + } else { + (false, below, above) + }); + } + + let above_change_offset = (above.added.lines.len() as i64 - above.removed.lines.len() as i64) + * if first_above { -1 } else { 1 }; + below.added.start = (below.added.start as i64 + above_change_offset) as usize; + below.removed.start = (below.removed.start as i64 + above_change_offset) as usize; + + Ok(if first_above { + (true, below, above) + } else { + (true, above, below) + }) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_commute() { + let hunk1 = owned::Hunk { + added: owned::Block { + start: 2, + lines: vec![b"bar\n".to_vec()], + trailing_newline: true, + }, + removed: owned::Block { + start: 1, + lines: vec![], + trailing_newline: true, + }, + }; + + let hunk2 = owned::Hunk { + added: owned::Block { + start: 1, + lines: vec![b"bar\n".to_vec()], + trailing_newline: true, + }, + removed: owned::Block { + start: 0, + lines: vec![], + trailing_newline: true, + }, + }; + + let (res, new1, new2) = commute(hunk1, hunk2).unwrap(); + assert!(res); + assert_eq!(new1.added.start, 1); + assert_eq!(new2.added.start, 3); + } +} @@ -5,6 +5,7 @@ extern crate slog; mod owned; mod stack; +mod commute; pub struct Config<'a> { pub dry_run: bool, @@ -66,43 +67,3 @@ pub fn run(config: &Config) -> Result<(), failure::Error> { Ok(()) } - -fn overlap(above: &owned::Block, below: &owned::Block) -> bool { - !above.lines.is_empty() && !below.lines.is_empty() - && below.start - above.start - above.lines.len() == 0 -} - -fn commute( - first: owned::Hunk, - second: owned::Hunk, -) -> Result<(bool, owned::Hunk, owned::Hunk), failure::Error> { - // represent hunks in content order rather than application order - let (first_above, above, mut below) = match ( - first.added.start <= second.added.start, - first.removed.start <= second.removed.start, - ) { - (true, true) => (true, first, second), - (false, false) => (false, second, first), - _ => return Err(failure::err_msg("nonsensical hunk ordering")), - }; - - // if the hunks overlap on either side, they can't commute, so return them in original order - if overlap(&above.added, &below.added) || overlap(&above.removed, &below.removed) { - return Ok(if first_above { - (false, above, below) - } else { - (false, below, above) - }); - } - - let above_change_offset = (above.added.lines.len() as i64 - above.removed.lines.len() as i64) - * if first_above { -1 } else { 1 }; - below.added.start = (below.added.start as i64 + above_change_offset) as usize; - below.removed.start = (below.removed.start as i64 + above_change_offset) as usize; - - Ok(if first_above { - (true, below, above) - } else { - (true, above, below) - }) -} |