Skip to main content

gn_cli/commands/
diff.rs

1use anyhow::Result;
2use clap::Args;
3use gn_core::namespace::Namespace;
4use gn_core::NotesEngine;
5use std::collections::HashMap;
6use std::process::Command;
7
8#[derive(Args, Debug)]
9pub struct DiffArgs {
10    /// Target commit, branch, or file to diff
11    pub target: Option<String>,
12
13    /// Namespace to check notes from (default: all)
14    #[arg(short, long, default_value = "comments")]
15    pub namespace: String,
16}
17
18pub fn run(args: &DiffArgs) -> Result<()> {
19    let engine = NotesEngine::new(".");
20    let ns = Namespace::from_str(&args.namespace);
21    let notes = engine.read_notes(&ns).unwrap_or_default();
22
23    // Map: file -> list of notes
24    let mut notes_by_file: HashMap<String, Vec<&gn_core::note::Note>> = HashMap::new();
25    for note in &notes {
26        if let Some(ref f) = note.file {
27            notes_by_file.entry(f.clone()).or_default().push(note);
28        }
29    }
30
31    let mut git_cmd = Command::new("git");
32    git_cmd.arg("diff");
33    if let Some(ref t) = args.target {
34        git_cmd.arg(t);
35    }
36
37    let output = git_cmd.output()?;
38    let diff_text = String::from_utf8_lossy(&output.stdout);
39
40    if diff_text.trim().is_empty() {
41        println!("No diff found.");
42        return Ok(());
43    }
44
45    let mut current_file: Option<String> = None;
46
47    for line in diff_text.lines() {
48        if line.starts_with("+++ b/") {
49            let f = line.trim_start_matches("+++ b/").to_string();
50            current_file = Some(f);
51            println!("{}", line);
52            continue;
53        }
54
55        if line.starts_with("@@ ") {
56            println!("\x1b[36m{}\x1b[0m", line);
57            // Check if current file has notes
58            if let Some(ref f) = current_file {
59                if let Some(file_notes) = notes_by_file.get(f) {
60                    for n in file_notes {
61                        let id_short = &n.id.to_string()[..8];
62                        let line_str = n.line_start.map(|l| format!(":{}", l)).unwrap_or_default();
63                        println!(
64                            "  \x1b[33m💬 Note [{}] on {}{} by {}: \"{}\"\x1b[0m",
65                            id_short,
66                            f,
67                            line_str,
68                            n.author.split('<').next().unwrap_or(&n.author).trim(),
69                            n.body.replace('\n', " ").chars().take(60).collect::<String>()
70                        );
71                    }
72                }
73            }
74            continue;
75        }
76
77        if line.starts_with('+') {
78            println!("\x1b[32m{}\x1b[0m", line);
79        } else if line.starts_with('-') {
80            println!("\x1b[31m{}\x1b[0m", line);
81        } else {
82            println!("{}", line);
83        }
84    }
85
86    Ok(())
87}