orch/engine/runner/
diff.rs1use std::collections::BTreeMap;
2
3#[derive(Debug, Clone, PartialEq, Eq)]
4pub struct DiffHunk {
5 pub old_start: u32,
6 pub old_count: u32,
7 pub new_start: u32,
8 pub new_count: u32,
9}
10
11#[derive(Debug, Clone, PartialEq, Eq)]
12pub struct FileDiffSummary {
13 pub path: String,
14 pub hunks: Vec<DiffHunk>,
15}
16
17pub fn parse_unified_diff_hunks(diff: &str) -> Vec<FileDiffSummary> {
18 let mut by_file: BTreeMap<String, Vec<DiffHunk>> = BTreeMap::new();
19 let mut current_file: Option<String> = None;
20
21 for line in diff.lines() {
22 if let Some(path) = line.strip_prefix("+++ b/") {
23 current_file = Some(path.to_string());
24 by_file.entry(path.to_string()).or_default();
25 continue;
26 }
27
28 if let Some(rest) = line.strip_prefix("@@ -") {
29 let Some(file) = current_file.as_ref() else {
30 continue;
31 };
32 if let Some(hunk) = parse_hunk_header(rest) {
33 by_file.entry(file.clone()).or_default().push(hunk);
34 }
35 }
36 }
37
38 by_file
39 .into_iter()
40 .map(|(path, hunks)| FileDiffSummary { path, hunks })
41 .collect()
42}
43
44fn parse_hunk_header(rest: &str) -> Option<DiffHunk> {
45 let (old_part, remainder) = rest.split_once(" +")?;
46 let (new_part, _) = remainder.split_once(" @@")?;
47
48 let (old_start, old_count) = parse_range(old_part)?;
49 let (new_start, new_count) = parse_range(new_part)?;
50
51 Some(DiffHunk {
52 old_start,
53 old_count,
54 new_start,
55 new_count,
56 })
57}
58
59fn parse_range(part: &str) -> Option<(u32, u32)> {
60 if let Some((start, count)) = part.split_once(',') {
61 Some((start.parse().ok()?, count.parse().ok()?))
62 } else {
63 Some((part.parse().ok()?, 1))
64 }
65}
66
67#[cfg(test)]
68mod tests {
69 use super::parse_unified_diff_hunks;
70
71 #[test]
72 fn parses_multi_file_hunks() {
73 let diff = r#"diff --git a/src/a.rs b/src/a.rs
74index 1111111..2222222 100644
75--- a/src/a.rs
76+++ b/src/a.rs
77@@ -1,1 +1,2 @@
78+line
79@@ -10,2 +11,3 @@
80-line
81+line
82+line2
83diff --git a/src/b.rs b/src/b.rs
84index 3333333..4444444 100644
85--- a/src/b.rs
86+++ b/src/b.rs
87@@ -5 +5 @@
88-old
89+new
90"#;
91
92 let files = parse_unified_diff_hunks(diff);
93 assert_eq!(files.len(), 2);
94 assert_eq!(files[0].path, "src/a.rs");
95 assert_eq!(files[0].hunks.len(), 2);
96 assert_eq!(files[1].path, "src/b.rs");
97 assert_eq!(files[1].hunks.len(), 1);
98 }
99}