1use crate::effect::Effect;
9use crate::store::Store;
10use serde::Serialize;
11use similar::{ChangeTag, TextDiff};
12use std::fs;
13use std::path::Path;
14
15#[derive(Debug, Serialize)]
17pub struct DiffEntry {
18 pub path: String,
19 pub status: String,
21 pub added: usize,
22 pub removed: usize,
23 pub hunk: String,
25}
26
27pub fn diff_effects(effects: &[Effect], store: &Store) -> Vec<DiffEntry> {
29 let mut out = vec![];
30 for e in effects {
31 match e {
32 Effect::File {
33 path, prev_blob, ..
34 } => {
35 let before = store.get(prev_blob).unwrap_or_default();
36 match fs::read(path) {
37 Ok(after) if after == before => {} Ok(after) => out.push(make(path, &before, &after, "modified")),
39 Err(_) => out.push(make(path, &before, &[], "deleted")),
40 }
41 }
42 Effect::PathCreate { path } => {
43 if let Ok(after) = fs::read(path) {
44 out.push(make(path, &[], &after, "created"));
45 }
46 }
47 Effect::Symlink { path, target } => out.push(DiffEntry {
48 path: path.display().to_string(),
49 status: "symlink".into(),
50 added: 0,
51 removed: 0,
52 hunk: format!("→ {}", target.display()),
53 }),
54 _ => {}
57 }
58 }
59 out
60}
61
62fn make(path: &Path, before: &[u8], after: &[u8], status: &str) -> DiffEntry {
63 let display = path.display().to_string();
64 let (Ok(b), Ok(a)) = (std::str::from_utf8(before), std::str::from_utf8(after)) else {
65 return DiffEntry {
66 path: display,
67 status: "binary".into(),
68 added: 0,
69 removed: 0,
70 hunk: "(binary file changed)".into(),
71 };
72 };
73
74 let td = TextDiff::from_lines(b, a);
75 let mut added = 0;
76 let mut removed = 0;
77 for c in td.iter_all_changes() {
78 match c.tag() {
79 ChangeTag::Insert => added += 1,
80 ChangeTag::Delete => removed += 1,
81 ChangeTag::Equal => {}
82 }
83 }
84 let hunk = td.unified_diff().context_radius(3).to_string();
85
86 DiffEntry {
87 path: display,
88 status: status.into(),
89 added,
90 removed,
91 hunk,
92 }
93}