Skip to content

Commit f7842d1

Browse files
committed
unified: Count bare-newline context lines in hunk ranges
iter_file_patch tracked how far it had progressed through a hunk by decrementing orig_range/mod_range only for lines starting with ' ', '+' or '-'. Blank context lines emitted as a bare "\n" (without the leading space, as quilt-refreshed and handwritten patches often do) therefore didn't count, leaving the counter above zero when the hunk body had actually finished. In a multi-file patch that meant the next file's Index:/---/+++ lines were absorbed into the previous hunk's body, so the Index: line ended up fed to Hunk::from_header and failed with "Malformed hunk header: Does not match format." Count a bare newline as a context line in both ranges so grouping lines up with the real hunk size.
1 parent 427d0dd commit f7842d1

1 file changed

Lines changed: 42 additions & 2 deletions

File tree

‎src/unified.rs‎

Lines changed: 42 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -916,10 +916,16 @@ where
916916
// would otherwise underflow these counters. The real
917917
// malformed-hunk error is surfaced downstream by
918918
// `iter_hunks`; here we just keep grouping lines.
919-
if line.starts_with(b"-") || line.starts_with(b" ") {
919+
//
920+
// A bare `\n` is a blank context line that was emitted with
921+
// its leading space stripped (common in handwritten and
922+
// quilt-refreshed patches); count it like a space-prefixed
923+
// context line in both ranges.
924+
let is_blank_context = line.as_slice() == b"\n";
925+
if line.starts_with(b"-") || line.starts_with(b" ") || is_blank_context {
920926
self.orig_range = self.orig_range.saturating_sub(1);
921927
}
922-
if line.starts_with(b"+") || line.starts_with(b" ") {
928+
if line.starts_with(b"+") || line.starts_with(b" ") || is_blank_context {
923929
self.mod_range = self.mod_range.saturating_sub(1);
924930
}
925931
self.saved_lines.push(line);
@@ -1140,6 +1146,40 @@ mod iter_file_patch_tests {
11401146
results.iter().filter(|r| r.is_ok()).count(),
11411147
);
11421148
}
1149+
1150+
/// Blank context lines in a hunk body are sometimes emitted as a bare
1151+
/// `\n` with the leading space stripped (common in handwritten or
1152+
/// quilt-refreshed patches). `iter_file_patch` must count those in the
1153+
/// hunk's range: otherwise the next file's `Index:`/`---`/`+++` header
1154+
/// is swallowed into the previous hunk's body and parsing of the second
1155+
/// file fails with `Malformed hunk header: Does not match format.`.
1156+
#[test]
1157+
fn test_blank_context_line_counts_in_hunk_range() {
1158+
let lines = [
1159+
"Index: one\n",
1160+
"===================================================================\n",
1161+
"--- a/one\n",
1162+
"+++ b/one\n",
1163+
"@@ -1,3 +1,2 @@\n",
1164+
" ctx1\n",
1165+
"\n", // blank context line, no leading space
1166+
"-removed\n",
1167+
"Index: two\n",
1168+
"===================================================================\n",
1169+
"--- a/two\n",
1170+
"+++ b/two\n",
1171+
"@@ -1,1 +1,1 @@\n",
1172+
"-x\n",
1173+
"+y\n",
1174+
];
1175+
let results: Vec<_> =
1176+
super::parse_patches(lines.into_iter().map(|l| l.as_bytes().to_vec())).collect();
1177+
let patches: Vec<_> = results
1178+
.into_iter()
1179+
.collect::<Result<Vec<_>, _>>()
1180+
.expect("blank context line should not break multi-file parsing");
1181+
assert_eq!(patches.len(), 2);
1182+
}
11431183
}
11441184

11451185
/// A patch that can be applied to a single file

0 commit comments

Comments
 (0)