diff --git a/cli/src/diff_util.rs b/cli/src/diff_util.rs index 48c6dfcb4..17e226d2e 100644 --- a/cli/src/diff_util.rs +++ b/cli/src/diff_util.rs @@ -41,6 +41,7 @@ use jj_lib::copies::CopyOperation; use jj_lib::copies::CopyRecords; use jj_lib::diff::Diff; use jj_lib::diff::DiffHunk; +use jj_lib::diff::DiffHunkKind; use jj_lib::files::DiffLineHunkSide; use jj_lib::files::DiffLineIterator; use jj_lib::files::DiffLineNumber; @@ -469,9 +470,13 @@ fn show_color_words_diff_hunks( let mut emitted = false; for hunk in line_diff.hunks() { - match hunk { - DiffHunk::Matching(content) => contexts.push(content), - DiffHunk::Different(contents) => { + match hunk.kind { + DiffHunkKind::Matching => { + // TODO: add support for unmatched contexts + debug_assert!(hunk.contents.iter().all_equal()); + contexts.push(hunk.contents[0]); + } + DiffHunkKind::Different => { let num_after = if emitted { options.context } else { 0 }; line_number = show_color_words_context_lines( formatter, @@ -483,7 +488,7 @@ fn show_color_words_diff_hunks( contexts.clear(); emitted = true; line_number = - show_color_words_diff_lines(formatter, &contents, line_number, options)?; + show_color_words_diff_lines(formatter, &hunk.contents, line_number, options)?; } } } @@ -656,9 +661,9 @@ fn show_color_words_single_sided_line( fn count_diff_alternation(diff_hunks: &[DiffHunk]) -> usize { diff_hunks .iter() - .filter_map(|hunk| match hunk { - DiffHunk::Matching(_) => None, - DiffHunk::Different(contents) => Some(contents), + .filter_map(|hunk| match hunk.kind { + DiffHunkKind::Matching => None, + DiffHunkKind::Different => Some(&hunk.contents), }) // Map non-empty diff side to index (0: left, 1: right) .flat_map(|contents| contents.iter().positions(|content| !content.is_empty())) @@ -671,9 +676,9 @@ fn count_diff_alternation(diff_hunks: &[DiffHunk]) -> usize { fn split_diff_hunks_by_matching_newline<'a, 'b>( diff_hunks: &'a [DiffHunk<'b>], ) -> impl Iterator]> { - diff_hunks.split_inclusive(|hunk| match hunk { - DiffHunk::Matching(content) => content.contains(&b'\n'), - DiffHunk::Different(_) => false, + diff_hunks.split_inclusive(|hunk| match hunk.kind { + DiffHunkKind::Matching => hunk.contents.iter().all(|content| content.contains(&b'\n')), + DiffHunkKind::Different => false, }) } @@ -1144,9 +1149,11 @@ fn unified_diff_hunks<'content>( let diff = Diff::by_line([left_content, right_content]); let mut diff_hunks = diff.hunks().peekable(); while let Some(hunk) = diff_hunks.next() { - match hunk { - DiffHunk::Matching(content) => { - let mut lines = content.split_inclusive(|b| *b == b'\n').fuse(); + match hunk.kind { + DiffHunkKind::Matching => { + // TODO: add support for unmatched contexts + debug_assert!(hunk.contents.iter().all_equal()); + let mut lines = hunk.contents[0].split_inclusive(|b| *b == b'\n').fuse(); if !current_hunk.lines.is_empty() { // The previous hunk line should be either removed/added. current_hunk.extend_context_lines(lines.by_ref().take(num_context_lines)); @@ -1172,9 +1179,9 @@ fn unified_diff_hunks<'content>( // The next hunk should be of DiffHunk::Different type if any. current_hunk.extend_context_lines(before_lines.into_iter().rev()); } - DiffHunk::Different(contents) => { + DiffHunkKind::Different => { let (left_lines, right_lines) = - unzip_diff_hunks_to_lines(Diff::by_word(contents).hunks()); + unzip_diff_hunks_to_lines(Diff::by_word(hunk.contents).hunks()); current_hunk.extend_removed_lines(left_lines); current_hunk.extend_added_lines(right_lines); } @@ -1200,9 +1207,12 @@ where let mut right_tokens: DiffTokenVec<'content> = vec![]; for hunk in diff_hunks { - match hunk.borrow() { - DiffHunk::Matching(content) => { - for token in content.split_inclusive(|b| *b == b'\n') { + let hunk = hunk.borrow(); + match hunk.kind { + DiffHunkKind::Matching => { + // TODO: add support for unmatched contexts + debug_assert!(hunk.contents.iter().all_equal()); + for token in hunk.contents[0].split_inclusive(|b| *b == b'\n') { left_tokens.push((DiffTokenType::Matching, token)); right_tokens.push((DiffTokenType::Matching, token)); if token.ends_with(b"\n") { @@ -1211,8 +1221,8 @@ where } } } - DiffHunk::Different(contents) => { - let [left, right] = contents[..] + DiffHunkKind::Different => { + let [left, right] = hunk.contents[..] .try_into() .expect("hunk should have exactly two inputs"); for token in left.split_inclusive(|b| *b == b'\n') { @@ -1437,10 +1447,10 @@ fn get_diff_stat( let mut added = 0; let mut removed = 0; for hunk in diff.hunks() { - match hunk { - DiffHunk::Matching(_) => {} - DiffHunk::Different(contents) => { - let [left, right] = contents.try_into().unwrap(); + match hunk.kind { + DiffHunkKind::Matching => {} + DiffHunkKind::Different => { + let [left, right] = hunk.contents.try_into().unwrap(); removed += left.split_inclusive(|b| *b == b'\n').count(); added += right.split_inclusive(|b| *b == b'\n').count(); } diff --git a/cli/src/merge_tools/builtin.rs b/cli/src/merge_tools/builtin.rs index 14c7e6171..8631a567c 100644 --- a/cli/src/merge_tools/builtin.rs +++ b/cli/src/merge_tools/builtin.rs @@ -16,7 +16,7 @@ use jj_lib::conflicts::materialize_merge_result; use jj_lib::conflicts::materialize_tree_value; use jj_lib::conflicts::MaterializedTreeValue; use jj_lib::diff::Diff; -use jj_lib::diff::DiffHunk; +use jj_lib::diff::DiffHunkKind; use jj_lib::files; use jj_lib::files::MergeResult; use jj_lib::matchers::Matcher; @@ -253,8 +253,10 @@ fn make_diff_sections( let diff = Diff::by_line([left_contents.as_bytes(), right_contents.as_bytes()]); let mut sections = Vec::new(); for hunk in diff.hunks() { - match hunk { - DiffHunk::Matching(text) => { + match hunk.kind { + DiffHunkKind::Matching => { + debug_assert!(hunk.contents.iter().all_equal()); + let text = hunk.contents[0]; let text = std::str::from_utf8(text).map_err(|err| BuiltinToolError::DecodeUtf8 { source: err, @@ -267,7 +269,8 @@ fn make_diff_sections( .collect(), }); } - DiffHunk::Different(sides) => { + DiffHunkKind::Different => { + let sides = &hunk.contents; assert_eq!(sides.len(), 2, "only two inputs were provided to the diff"); let left_side = std::str::from_utf8(sides[0]).map_err(|err| BuiltinToolError::DecodeUtf8 { diff --git a/lib/src/conflicts.rs b/lib/src/conflicts.rs index 2d8503346..958d96ed4 100644 --- a/lib/src/conflicts.rs +++ b/lib/src/conflicts.rs @@ -40,6 +40,7 @@ use crate::copies::CopiesTreeDiffEntry; use crate::copies::CopiesTreeDiffEntryPath; use crate::diff::Diff; use crate::diff::DiffHunk; +use crate::diff::DiffHunkKind; use crate::files; use crate::files::MergeResult; use crate::merge::Merge; @@ -73,19 +74,20 @@ static CONFLICT_MARKER_REGEX: once_cell::sync::Lazy = once_cell::sync::La fn write_diff_hunks(hunks: &[DiffHunk], file: &mut dyn Write) -> std::io::Result<()> { for hunk in hunks { - match hunk { - DiffHunk::Matching(content) => { - for line in content.split_inclusive(|b| *b == b'\n') { + match hunk.kind { + DiffHunkKind::Matching => { + debug_assert!(hunk.contents.iter().all_equal()); + for line in hunk.contents[0].split_inclusive(|b| *b == b'\n') { file.write_all(b" ")?; file.write_all(line)?; } } - DiffHunk::Different(content) => { - for line in content[0].split_inclusive(|b| *b == b'\n') { + DiffHunkKind::Different => { + for line in hunk.contents[0].split_inclusive(|b| *b == b'\n') { file.write_all(b"-")?; file.write_all(line)?; } - for line in content[1].split_inclusive(|b| *b == b'\n') { + for line in hunk.contents[1].split_inclusive(|b| *b == b'\n') { file.write_all(b"+")?; file.write_all(line)?; } @@ -333,9 +335,9 @@ pub fn materialize_merge_result( fn diff_size(hunks: &[DiffHunk]) -> usize { hunks .iter() - .map(|hunk| match hunk { - DiffHunk::Matching(_) => 0, - DiffHunk::Different(slices) => slices.iter().map(|slice| slice.len()).sum(), + .map(|hunk| match hunk.kind { + DiffHunkKind::Matching => 0, + DiffHunkKind::Different => hunk.contents.iter().map(|content| content.len()).sum(), }) .sum() } diff --git a/lib/src/diff.rs b/lib/src/diff.rs index 95d2c4f11..209c070ae 100644 --- a/lib/src/diff.rs +++ b/lib/src/diff.rs @@ -721,6 +721,14 @@ impl<'input> Diff<'input> { } } + /// Returns contents at the unchanged `range`. + fn hunk_at<'a>(&'a self, range: &'a UnchangedRange) -> impl Iterator + 'a { + itertools::chain( + iter::once(&self.base_input[range.base.clone()]), + iter::zip(&self.other_inputs, &range.others).map(|(input, r)| &input[r.clone()]), + ) + } + /// Returns contents between the `previous` ends and the `current` starts. fn hunk_between<'a>( &'a self, @@ -803,24 +811,38 @@ impl<'input> Diff<'input> { } } -#[derive(PartialEq, Eq, Clone, Debug)] -pub enum DiffHunk<'input> { - Matching(&'input BStr), - Different(Vec<&'input BStr>), +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct DiffHunk<'input> { + pub kind: DiffHunkKind, + pub contents: Vec<&'input BStr>, } impl<'input> DiffHunk<'input> { - pub fn matching + ?Sized>(content: &'input T) -> Self { - DiffHunk::Matching(BStr::new(content)) + pub fn matching + ?Sized + 'input>( + contents: impl IntoIterator, + ) -> Self { + DiffHunk { + kind: DiffHunkKind::Matching, + contents: contents.into_iter().map(BStr::new).collect(), + } } pub fn different + ?Sized + 'input>( contents: impl IntoIterator, ) -> Self { - DiffHunk::Different(contents.into_iter().map(BStr::new).collect()) + DiffHunk { + kind: DiffHunkKind::Different, + contents: contents.into_iter().map(BStr::new).collect(), + } } } +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub enum DiffHunkKind { + Matching, + Different, +} + pub struct DiffHunkIterator<'diff, 'input> { diff: &'diff Diff<'input>, previous: UnchangedRange, @@ -835,21 +857,22 @@ impl<'diff, 'input> Iterator for DiffHunkIterator<'diff, 'input> { loop { if !self.unchanged_emitted { self.unchanged_emitted = true; - if !self.previous.base.is_empty() { - return Some(DiffHunk::Matching( - &self.diff.base_input[self.previous.base.clone()], - )); + let contents = self.diff.hunk_at(&self.previous).collect_vec(); + if contents.iter().any(|content| !content.is_empty()) { + let kind = DiffHunkKind::Matching; + return Some(DiffHunk { kind, contents }); } } if let Some(current) = self.unchanged_iter.next() { - let slices = self + let contents = self .diff .hunk_between(&self.previous, current) .collect_vec(); self.previous = current.clone(); self.unchanged_emitted = false; - if slices.iter().any(|slice| !slice.is_empty()) { - return Some(DiffHunk::Different(slices)); + if contents.iter().any(|content| !content.is_empty()) { + let kind = DiffHunkKind::Different; + return Some(DiffHunk { kind, contents }); } } else { break; @@ -1188,7 +1211,7 @@ mod tests { #[test] fn test_diff_single_input() { - assert_eq!(diff(["abc"]), vec![DiffHunk::matching("abc")]); + assert_eq!(diff(["abc"]), vec![DiffHunk::matching(["abc"])]); } #[test] @@ -1228,9 +1251,9 @@ mod tests { assert_eq!( diff(["a b c", "a X c"]), vec![ - DiffHunk::matching("a "), + DiffHunk::matching(["a "].repeat(2)), DiffHunk::different(["b", "X"]), - DiffHunk::matching(" c"), + DiffHunk::matching([" c"].repeat(2)), ] ); } @@ -1240,9 +1263,9 @@ mod tests { assert_eq!( diff(["a b c", "a X c", "a b c"]), vec![ - DiffHunk::matching("a "), + DiffHunk::matching(["a "].repeat(3)), DiffHunk::different(["b", "X", "b"]), - DiffHunk::matching(" c"), + DiffHunk::matching([" c"].repeat(3)), ] ); } @@ -1252,9 +1275,9 @@ mod tests { assert_eq!( diff(["a b c", "a X c", "a c X"]), vec![ - DiffHunk::matching("a "), + DiffHunk::matching(["a "].repeat(3)), DiffHunk::different(["b ", "X ", ""]), - DiffHunk::matching("c"), + DiffHunk::matching(["c"].repeat(3)), DiffHunk::different(["", "", " X"]), ] ); @@ -1271,9 +1294,9 @@ mod tests { assert_eq!( diff.hunks().collect_vec(), vec![ - DiffHunk::matching("a\nb\nc\n"), + DiffHunk::matching(["a\nb\nc\n"].repeat(2)), DiffHunk::different(["d\n", "X\n"]), - DiffHunk::matching("e\nf\ng"), + DiffHunk::matching(["e\nf\ng"].repeat(2)), ] ); } @@ -1291,9 +1314,9 @@ mod tests { assert_eq!( diff(["a z", "a S z"]), vec![ - DiffHunk::matching("a "), + DiffHunk::matching(["a "].repeat(2)), DiffHunk::different(["", "S "]), - DiffHunk::matching("z"), + DiffHunk::matching(["z"].repeat(2)), ] ); } @@ -1303,11 +1326,11 @@ mod tests { assert_eq!( diff(["a R R S S z", "a S S R R z"]), vec![ - DiffHunk::matching("a "), + DiffHunk::matching(["a "].repeat(2)), DiffHunk::different(["R R ", ""]), - DiffHunk::matching("S S "), + DiffHunk::matching(["S S "].repeat(2)), DiffHunk::different(["", "R R "]), - DiffHunk::matching("z") + DiffHunk::matching(["z"].repeat(2)) ], ); } @@ -1320,19 +1343,19 @@ mod tests { "a r r x q y z q b y q x r r c", ]), vec![ - DiffHunk::matching("a "), + DiffHunk::matching(["a "].repeat(2)), DiffHunk::different(["q", "r"]), - DiffHunk::matching(" "), + DiffHunk::matching([" "].repeat(2)), DiffHunk::different(["", "r "]), - DiffHunk::matching("x q y "), + DiffHunk::matching(["x q y "].repeat(2)), DiffHunk::different(["q ", ""]), - DiffHunk::matching("z q b "), + DiffHunk::matching(["z q b "].repeat(2)), DiffHunk::different(["q ", ""]), - DiffHunk::matching("y q x "), + DiffHunk::matching(["y q x "].repeat(2)), DiffHunk::different(["q", "r"]), - DiffHunk::matching(" "), + DiffHunk::matching([" "].repeat(2)), DiffHunk::different(["", "r "]), - DiffHunk::matching("c"), + DiffHunk::matching(["c"].repeat(2)), ] ); } @@ -1346,17 +1369,23 @@ mod tests { } assert_eq!(diff(["", "\n"]), vec![DiffHunk::different(["", "\n"])]); - assert_eq!(diff(["a\n", " a\r\n"]), vec![DiffHunk::matching("a\n")]); + assert_eq!( + diff(["a\n", " a\r\n"]), + vec![DiffHunk::matching(["a\n", " a\r\n"])] + ); assert_eq!( diff(["a\n", " a\nb"]), - vec![DiffHunk::matching("a\n"), DiffHunk::different(["", "b"])] + vec![ + DiffHunk::matching(["a\n", " a\n"]), + DiffHunk::different(["", "b"]), + ] ); // No LCS matches, so trim leading/trailing common lines assert_eq!( diff(["a\nc\n", " a\n a\n"]), vec![ - DiffHunk::matching("a\n"), + DiffHunk::matching(["a\n", " a\n"]), DiffHunk::different(["c\n", " a\n"]), ] ); @@ -1364,7 +1393,7 @@ mod tests { diff(["c\na\n", " a\n a\n"]), vec![ DiffHunk::different(["c\n", " a\n"]), - DiffHunk::matching("a\n"), + DiffHunk::matching(["a\n", " a\n"]), ] ); } @@ -1379,7 +1408,10 @@ mod tests { assert_eq!(diff(["", "\n"]), vec![DiffHunk::different(["", "\n"])]); // whitespace at line end is ignored - assert_eq!(diff(["a\n", "a\r\n"]), vec![DiffHunk::matching("a\n")]); + assert_eq!( + diff(["a\n", "a\r\n"]), + vec![DiffHunk::matching(["a\n", "a\r\n"])] + ); // but whitespace at line start isn't assert_eq!( diff(["a\n", " a\n"]), @@ -1387,7 +1419,10 @@ mod tests { ); assert_eq!( diff(["a\n", "a \nb"]), - vec![DiffHunk::matching("a\n"), DiffHunk::different(["", "b"])] + vec![ + DiffHunk::matching(["a\n", "a \n"]), + DiffHunk::different(["", "b"]), + ] ); } @@ -1405,11 +1440,11 @@ mod tests { " pub fn write_fmt(&mut self, fmt: fmt::Arguments<\'_>) -> io::Result<()> {\n self.styler().write_fmt(fmt)\n" ]), vec![ - DiffHunk::matching(" pub fn write_fmt(&mut self, fmt: fmt::Arguments<\'_>) "), + DiffHunk::matching([" pub fn write_fmt(&mut self, fmt: fmt::Arguments<\'_>) "].repeat(2)), DiffHunk::different(["", "-> io::Result<()> "]), - DiffHunk::matching("{\n self.styler().write_fmt(fmt)"), + DiffHunk::matching(["{\n self.styler().write_fmt(fmt)"].repeat(2)), DiffHunk::different([".unwrap()", ""]), - DiffHunk::matching("\n") + DiffHunk::matching(["\n"].repeat(2)) ] ); } @@ -1560,19 +1595,19 @@ int main(int argc, char **argv) "##, ]), vec![ - DiffHunk::matching("/*\n * GIT - The information manager from hell\n *\n * Copyright (C) Linus Torvalds, 2005\n */\n#include \"#cache.h\"\n\n"), + DiffHunk::matching(["/*\n * GIT - The information manager from hell\n *\n * Copyright (C) Linus Torvalds, 2005\n */\n#include \"#cache.h\"\n\n"].repeat(2)), DiffHunk::different(["", "static void create_directories(const char *path)\n{\n\tint len = strlen(path);\n\tchar *buf = malloc(len + 1);\n\tconst char *slash = path;\n\n\twhile ((slash = strchr(slash+1, \'/\')) != NULL) {\n\t\tlen = slash - path;\n\t\tmemcpy(buf, path, len);\n\t\tbuf[len] = 0;\n\t\tmkdir(buf, 0700);\n\t}\n}\n\nstatic int create_file(const char *path)\n{\n\tint fd = open(path, O_WRONLY | O_TRUNC | O_CREAT, 0600);\n\tif (fd < 0) {\n\t\tif (errno == ENOENT) {\n\t\t\tcreate_directories(path);\n\t\t\tfd = open(path, O_WRONLY | O_TRUNC | O_CREAT, 0600);\n\t\t}\n\t}\n\treturn fd;\n}\n\n"]), - DiffHunk::matching("static int unpack(unsigned char *sha1)\n{\n\tvoid *buffer;\n\tunsigned long size;\n\tchar type[20];\n\n\tbuffer = read_sha1_file(sha1, type, &size);\n\tif (!buffer)\n\t\tusage(\"unable to read sha1 file\");\n\tif (strcmp(type, \"tree\"))\n\t\tusage(\"expected a \'tree\' node\");\n\twhile (size) {\n\t\tint len = strlen(buffer)+1;\n\t\tunsigned char *sha1 = buffer + len;\n\t\tchar *path = strchr(buffer, \' \')+1;\n"), + DiffHunk::matching(["static int unpack(unsigned char *sha1)\n{\n\tvoid *buffer;\n\tunsigned long size;\n\tchar type[20];\n\n\tbuffer = read_sha1_file(sha1, type, &size);\n\tif (!buffer)\n\t\tusage(\"unable to read sha1 file\");\n\tif (strcmp(type, \"tree\"))\n\t\tusage(\"expected a \'tree\' node\");\n\twhile (size) {\n\t\tint len = strlen(buffer)+1;\n\t\tunsigned char *sha1 = buffer + len;\n\t\tchar *path = strchr(buffer, \' \')+1;\n"].repeat(2)), DiffHunk::different(["", "\t\tchar *data;\n\t\tunsigned long filesize;\n"]), - DiffHunk::matching("\t\tunsigned int mode;\n"), + DiffHunk::matching(["\t\tunsigned int mode;\n"].repeat(2)), DiffHunk::different(["", "\t\tint fd;\n\n"]), - DiffHunk::matching("\t\tif (size < len + 20 || sscanf(buffer, \"%o\", &mode) != 1)\n\t\t\tusage(\"corrupt \'tree\' file\");\n\t\tbuffer = sha1 + 20;\n\t\tsize -= len + 20;\n\t\t"), + DiffHunk::matching(["\t\tif (size < len + 20 || sscanf(buffer, \"%o\", &mode) != 1)\n\t\t\tusage(\"corrupt \'tree\' file\");\n\t\tbuffer = sha1 + 20;\n\t\tsize -= len + 20;\n\t\t"].repeat(2)), DiffHunk::different(["printf(\"%o %s (%s)\\n\", mode, path,", "data ="]), - DiffHunk::matching(" "), + DiffHunk::matching([" "].repeat(2)), DiffHunk::different(["sha1_to_hex", "read_sha1_file"]), - DiffHunk::matching("(sha1"), + DiffHunk::matching(["(sha1"].repeat(2)), DiffHunk::different([")", ", type, &filesize);\n\t\tif (!data || strcmp(type, \"blob\"))\n\t\t\tusage(\"tree file refers to bad file data\");\n\t\tfd = create_file(path);\n\t\tif (fd < 0)\n\t\t\tusage(\"unable to create file\");\n\t\tif (write(fd, data, filesize) != filesize)\n\t\t\tusage(\"unable to write file\");\n\t\tfchmod(fd, mode);\n\t\tclose(fd);\n\t\tfree(data"]), - DiffHunk::matching(");\n\t}\n\treturn 0;\n}\n\nint main(int argc, char **argv)\n{\n\tint fd;\n\tunsigned char sha1[20];\n\n\tif (argc != 2)\n\t\tusage(\"read-tree \");\n\tif (get_sha1_hex(argv[1], sha1) < 0)\n\t\tusage(\"read-tree \");\n\tsha1_file_directory = getenv(DB_ENVIRONMENT);\n\tif (!sha1_file_directory)\n\t\tsha1_file_directory = DEFAULT_DB_ENVIRONMENT;\n\tif (unpack(sha1) < 0)\n\t\tusage(\"unpack failed\");\n\treturn 0;\n}\n"), + DiffHunk::matching([");\n\t}\n\treturn 0;\n}\n\nint main(int argc, char **argv)\n{\n\tint fd;\n\tunsigned char sha1[20];\n\n\tif (argc != 2)\n\t\tusage(\"read-tree \");\n\tif (get_sha1_hex(argv[1], sha1) < 0)\n\t\tusage(\"read-tree \");\n\tsha1_file_directory = getenv(DB_ENVIRONMENT);\n\tif (!sha1_file_directory)\n\t\tsha1_file_directory = DEFAULT_DB_ENVIRONMENT;\n\tif (unpack(sha1) < 0)\n\t\tusage(\"unpack failed\");\n\treturn 0;\n}\n"].repeat(2)), ] ); } diff --git a/lib/src/files.rs b/lib/src/files.rs index f4125e507..ebb5ba31f 100644 --- a/lib/src/files.rs +++ b/lib/src/files.rs @@ -21,9 +21,11 @@ use std::mem; use bstr::BStr; use bstr::BString; +use itertools::Itertools as _; use crate::diff::Diff; use crate::diff::DiffHunk; +use crate::diff::DiffHunkKind; use crate::merge::trivial_merge; use crate::merge::Merge; @@ -130,8 +132,12 @@ where let Some(hunk) = self.diff_hunks.next() else { break; }; - match hunk.borrow() { - DiffHunk::Matching(text) => { + let hunk = hunk.borrow(); + match hunk.kind { + DiffHunkKind::Matching => { + // TODO: add support for unmatched contexts? + debug_assert!(hunk.contents.iter().all_equal()); + let text = hunk.contents[0]; let lines = text.split_inclusive(|b| *b == b'\n').map(BStr::new); for line in lines { self.current_line.hunks.push((DiffLineHunkSide::Both, line)); @@ -142,8 +148,8 @@ where } } } - DiffHunk::Different(contents) => { - let [left_text, right_text] = contents[..] + DiffHunkKind::Different => { + let [left_text, right_text] = hunk.contents[..] .try_into() .expect("hunk should have exactly two inputs"); let left_lines = left_text.split_inclusive(|b| *b == b'\n').map(BStr::new); @@ -201,11 +207,13 @@ fn merge_hunks(diff: &Diff, num_diffs: usize) -> MergeResult { let mut resolved_hunk = BString::new(vec![]); let mut merge_hunks: Vec> = vec![]; for diff_hunk in diff.hunks() { - match diff_hunk { - DiffHunk::Matching(content) => { - resolved_hunk.extend_from_slice(content); + match diff_hunk.kind { + DiffHunkKind::Matching => { + debug_assert!(diff_hunk.contents.iter().all_equal()); + resolved_hunk.extend_from_slice(diff_hunk.contents[0]); } - DiffHunk::Different(parts) => { + DiffHunkKind::Different => { + let parts = &diff_hunk.contents; if let Some(resolved) = trivial_merge(&parts[..num_diffs], &parts[num_diffs..]) { resolved_hunk.extend_from_slice(resolved); } else {