Skip to content

Commit 97fe9d3

Browse files
committed
Harden diff parsing and improve CLI help
1 parent 5d051b9 commit 97fe9d3

File tree

2 files changed

+81
-3
lines changed

2 files changed

+81
-3
lines changed

src/core/diff_parser.rs

Lines changed: 75 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -259,9 +259,37 @@ impl DiffParser {
259259
}
260260

261261
fn extract_file_path(line: &str) -> Result<String> {
262+
let re = regex::Regex::new(r#"^diff --git (?:"a/(.*?)"|a/(\S+)) (?:"b/(.*?)"|b/(\S+))"#)?;
263+
if let Some(caps) = re.captures(line) {
264+
let a_path = caps
265+
.get(1)
266+
.or_else(|| caps.get(2))
267+
.map(|m| m.as_str())
268+
.unwrap_or("");
269+
let b_path = caps
270+
.get(3)
271+
.or_else(|| caps.get(4))
272+
.map(|m| m.as_str())
273+
.unwrap_or("");
274+
275+
let chosen = if !b_path.is_empty() && b_path != "/dev/null" {
276+
b_path
277+
} else {
278+
a_path
279+
};
280+
return Ok(chosen.to_string());
281+
}
282+
262283
let parts: Vec<&str> = line.split_whitespace().collect();
263284
if parts.len() >= 4 {
264-
Ok(parts[2].trim_start_matches("a/").to_string())
285+
let a_path = parts[2].trim_start_matches("a/");
286+
let b_path = parts[3].trim_start_matches("b/");
287+
let chosen = if b_path != "/dev/null" {
288+
b_path
289+
} else {
290+
a_path
291+
};
292+
Ok(chosen.to_string())
265293
} else {
266294
anyhow::bail!("Invalid diff header: {}", line)
267295
}
@@ -272,7 +300,15 @@ impl DiffParser {
272300
.strip_prefix(prefix)
273301
.ok_or_else(|| anyhow::anyhow!("Invalid file header: {}", line))?
274302
.trim();
275-
let path = raw.split_whitespace().next().unwrap_or(raw);
303+
let path = if let Some(stripped) = raw.strip_prefix('"') {
304+
if let Some(end) = stripped.find('"') {
305+
&stripped[..end]
306+
} else {
307+
stripped
308+
}
309+
} else {
310+
raw.split_whitespace().next().unwrap_or(raw)
311+
};
276312
Ok(path
277313
.trim_start_matches("a/")
278314
.trim_start_matches("b/")
@@ -295,6 +331,10 @@ impl DiffParser {
295331
&& !lines[*i].starts_with("+++ ")
296332
{
297333
let line = lines[*i];
334+
if line.starts_with("\\ No newline at end of file") {
335+
*i += 1;
336+
continue;
337+
}
298338
if line.is_empty() {
299339
*i += 1;
300340
continue;
@@ -400,4 +440,37 @@ mod tests {
400440
assert_eq!(diffs[0].file_path, PathBuf::from("foo.txt"));
401441
assert_eq!(diffs[0].hunks.len(), 1);
402442
}
443+
444+
#[test]
445+
fn test_parse_diff_header_with_spaces() {
446+
let diff_text = "\
447+
diff --git \"a/foo bar.txt\" \"b/foo bar.txt\"\n\
448+
index 83db48f..f735c20 100644\n\
449+
--- \"a/foo bar.txt\"\n\
450+
+++ \"b/foo bar.txt\"\n\
451+
@@ -1,1 +1,1 @@\n\
452+
-hello\n\
453+
+world\n";
454+
455+
let diffs = DiffParser::parse_unified_diff(diff_text).unwrap();
456+
assert_eq!(diffs.len(), 1);
457+
assert_eq!(diffs[0].file_path, PathBuf::from("foo bar.txt"));
458+
}
459+
460+
#[test]
461+
fn test_parse_no_newline_marker() {
462+
let diff_text = "\
463+
diff --git a/foo.txt b/foo.txt\n\
464+
index 83db48f..f735c20 100644\n\
465+
--- a/foo.txt\n\
466+
+++ b/foo.txt\n\
467+
@@ -1,1 +1,1 @@\n\
468+
-hello\n\
469+
\\ No newline at end of file\n\
470+
+world\n";
471+
472+
let diffs = DiffParser::parse_unified_diff(diff_text).unwrap();
473+
assert_eq!(diffs.len(), 1);
474+
assert_eq!(diffs[0].hunks.len(), 1);
475+
}
403476
}

src/main.rs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,12 @@ struct Cli {
3232
#[arg(long, global = true)]
3333
max_tokens: Option<usize>,
3434

35-
#[arg(long, global = true, value_parser = clap::value_parser!(bool))]
35+
#[arg(
36+
long,
37+
global = true,
38+
value_parser = clap::value_parser!(bool),
39+
help = "Use OpenAI Responses API (true/false)"
40+
)]
3641
openai_responses: Option<bool>,
3742

3843
#[arg(long, global = true, default_value = "json")]

0 commit comments

Comments
 (0)