summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authortummychow <tummychow@users.noreply.github.com>2018-02-19 23:45:17 -0800
committertummychow <tummychow@users.noreply.github.com>2018-02-20 00:00:59 -0800
commit951bfce345cebeeeed8cff1f0c0c072347da3f38 (patch)
tree25a4f8ad92a45e4aec75abdb731420dfe9be435f
parent45ab83a57d5ffed8f58854c131a132d508341ace (diff)
fix commutation
-rw-r--r--src/commute.rs121
-rw-r--r--src/lib.rs41
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);
+ }
+}
diff --git a/src/lib.rs b/src/lib.rs
index 9157b9c..2ccde2e 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -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)
- })
-}