Skip to main content

chronicle/
import.rs

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/// Summary of an import operation.
10#[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
18/// Import annotations from a JSONL reader.
19///
20/// Each line is an `ExportEntry` JSON object. For each entry:
21/// 1. Check if the commit SHA exists locally.
22/// 2. If the commit has no existing note (or `force` is set), write the annotation.
23/// 3. Otherwise skip.
24pub 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        // Validate annotation
57        if entry.annotation.validate().is_err() {
58            summary.skipped_invalid += 1;
59            continue;
60        }
61
62        // Check if commit exists locally
63        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        // Check if note already exists
71        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}