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 #[arg(short, long)]
30 pub sign: bool,
31}
32
33pub fn run(args: &AddArgs) -> Result<()> {
34 let name_output = Command::new("git")
35 .args(["config", "user.name"])
36 .output()
37 .context("Failed to read user.name")?;
38 let email_output = Command::new("git")
39 .args(["config", "user.email"])
40 .output()
41 .context("Failed to read user.email")?;
42
43 let author = format!(
44 "{} <{}>",
45 String::from_utf8_lossy(&name_output.stdout).trim(),
46 String::from_utf8_lossy(&email_output.stdout).trim()
47 );
48
49 let head_output = Command::new("git")
50 .args(["rev-parse", "HEAD"])
51 .output()
52 .context("Failed to get HEAD commit")?;
53 let commit = String::from_utf8_lossy(&head_output.stdout)
54 .trim()
55 .to_string();
56
57 let mut line_start = None;
58 let mut line_end = None;
59 if !args.line.is_empty() {
60 if args.line.contains('-') {
61 let parts: Vec<&str> = args.line.split('-').collect();
62 if parts.len() == 2 {
63 line_start = parts[0].parse().ok();
64 line_end = parts[1].parse().ok();
65 }
66 } else {
67 line_start = args.line.parse().ok();
68 line_end = line_start;
69 }
70 }
71
72 let namespace = gn_core::Namespace::Custom(args.namespace.clone());
73
74 let mut note = Note::new(
75 commit,
76 Some(args.file.clone()),
77 line_start,
78 line_end,
79 args.message.clone(),
80 author,
81 namespace,
82 );
83
84 if let Some(thread_str) = &args.thread {
85 if let Ok(tid) = uuid::Uuid::parse_str(thread_str) {
86 note.thread_id = Some(tid);
87 }
88 }
89
90 if super::signing::is_signing_requested(args.sign) {
91 let payload = note.signing_payload();
92 let sig = super::signing::sign_payload(&payload)
93 .context("Failed to cryptographically sign note")?;
94 note.signature = Some(sig);
95 }
96
97 let engine = NotesEngine::new(".");
98 engine.write_note(¬e)?;
99
100 if note.signature.is_some() {
101 println!("✓ Note {} (signed) added to refs/notes/{}", note.id, args.namespace);
102 } else {
103 println!("✓ Note {} added to refs/notes/{}", note.id, args.namespace);
104 }
105
106 Ok(())
107}