1use anyhow::{Context, Result};
2use clap::Args;
3use gn_core::{Note, NotesEngine};
4use std::process::Command;
5
6#[derive(Args)]
7pub struct AddArgs {
8 #[arg(short, long)]
10 pub file: String,
11
12 #[arg(short, long)]
14 pub line: String,
15
16 #[arg(short, long)]
18 pub message: String,
19
20 #[arg(short, long, default_value = "comments")]
22 pub namespace: String,
23
24 #[arg(short, long)]
26 pub thread: Option<String>,
27}
28
29pub fn run(args: &AddArgs) -> Result<()> {
30 let name_output = Command::new("git")
31 .args(["config", "user.name"])
32 .output()
33 .context("Failed to read user.name")?;
34 let email_output = Command::new("git")
35 .args(["config", "user.email"])
36 .output()
37 .context("Failed to read user.email")?;
38
39 let author = format!(
40 "{} <{}>",
41 String::from_utf8_lossy(&name_output.stdout).trim(),
42 String::from_utf8_lossy(&email_output.stdout).trim()
43 );
44
45 let head_output = Command::new("git")
46 .args(["rev-parse", "HEAD"])
47 .output()
48 .context("Failed to get HEAD commit")?;
49 let commit = String::from_utf8_lossy(&head_output.stdout)
50 .trim()
51 .to_string();
52
53 let mut line_start = None;
54 let mut line_end = None;
55 if !args.line.is_empty() {
56 if args.line.contains('-') {
57 let parts: Vec<&str> = args.line.split('-').collect();
58 if parts.len() == 2 {
59 line_start = parts[0].parse().ok();
60 line_end = parts[1].parse().ok();
61 }
62 } else {
63 line_start = args.line.parse().ok();
64 line_end = line_start;
65 }
66 }
67
68 let namespace = gn_core::Namespace::Custom(args.namespace.clone());
69
70 let mut note = Note::new(
71 commit,
72 Some(args.file.clone()),
73 line_start,
74 line_end,
75 args.message.clone(),
76 author,
77 namespace,
78 );
79
80 if let Some(thread_str) = &args.thread {
81 if let Ok(tid) = uuid::Uuid::parse_str(thread_str) {
82 note.thread_id = Some(tid);
83 }
84 }
85
86 let engine = NotesEngine::new(".");
87 engine.write_note(¬e)?;
88
89 println!("✓ Note {} added to refs/notes/{}", note.id, args.namespace);
90
91 Ok(())
92}