diff: return matching hunk contents from all inputs

We're likely to use the right (or new) context lines in rendered diffs, but
it's odd that the hunks iterator choose which context hunk to return. We'll
also need both contents to calculate left/right line numbers.

Since the hunk content types are the same, I also split enum DiffHunk into
{ kind, contents } pair.
This commit is contained in:
Yuya Nishihara 2024-09-22 17:04:50 +09:00
parent cacd6a3fd0
commit 383cca4c4d
5 changed files with 154 additions and 96 deletions

View file

@ -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<Item = &'a [DiffHunk<'b>]> {
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();
}

View file

@ -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 {

View file

@ -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<Regex> = 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()
}

View file

@ -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<Item = &'input BStr> + '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<T: AsRef<[u8]> + ?Sized>(content: &'input T) -> Self {
DiffHunk::Matching(BStr::new(content))
pub fn matching<T: AsRef<[u8]> + ?Sized + 'input>(
contents: impl IntoIterator<Item = &'input T>,
) -> Self {
DiffHunk {
kind: DiffHunkKind::Matching,
contents: contents.into_iter().map(BStr::new).collect(),
}
}
pub fn different<T: AsRef<[u8]> + ?Sized + 'input>(
contents: impl IntoIterator<Item = &'input T>,
) -> 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 <key>\");\n\tif (get_sha1_hex(argv[1], sha1) < 0)\n\t\tusage(\"read-tree <key>\");\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 <key>\");\n\tif (get_sha1_hex(argv[1], sha1) < 0)\n\t\tusage(\"read-tree <key>\");\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)),
]
);
}

View file

@ -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<Merge<BString>> = 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 {