1use std::io::BufRead;
2
3use crate::error::chronicle_error::GitSnafu;
4use crate::error::Result;
5use crate::export::ExportEntry;
6use crate::git::GitOps;
7use snafu::ResultExt;
8
9#[derive(Debug, Clone)]
11pub struct ImportSummary {
12 pub imported: usize,
13 pub skipped_existing: usize,
14 pub skipped_not_found: usize,
15 pub skipped_invalid: usize,
16}
17
18pub fn import_annotations<R: BufRead>(
25 git_ops: &dyn GitOps,
26 reader: R,
27 force: bool,
28 dry_run: bool,
29) -> Result<ImportSummary> {
30 let mut summary = ImportSummary {
31 imported: 0,
32 skipped_existing: 0,
33 skipped_not_found: 0,
34 skipped_invalid: 0,
35 };
36
37 for line in reader.lines() {
38 let line = line.map_err(|e| crate::error::ChronicleError::Io {
39 source: e,
40 location: snafu::Location::default(),
41 })?;
42
43 let line = line.trim();
44 if line.is_empty() {
45 continue;
46 }
47
48 let entry: ExportEntry = match serde_json::from_str(line) {
49 Ok(e) => e,
50 Err(_) => {
51 summary.skipped_invalid += 1;
52 continue;
53 }
54 };
55
56 if entry.annotation.validate().is_err() {
58 summary.skipped_invalid += 1;
59 continue;
60 }
61
62 let commit_exists = git_ops.commit_info(&entry.commit_sha).is_ok();
64
65 if !commit_exists {
66 summary.skipped_not_found += 1;
67 continue;
68 }
69
70 if !force {
72 let has_note = git_ops.note_exists(&entry.commit_sha).context(GitSnafu)?;
73 if has_note {
74 summary.skipped_existing += 1;
75 continue;
76 }
77 }
78
79 if !dry_run {
80 let content = serde_json::to_string(&entry.annotation).map_err(|e| {
81 crate::error::ChronicleError::Json {
82 source: e,
83 location: snafu::Location::default(),
84 }
85 })?;
86 git_ops
87 .note_write(&entry.commit_sha, &content)
88 .context(GitSnafu)?;
89 }
90
91 summary.imported += 1;
92 }
93
94 Ok(summary)
95}