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 crate::schema;
8use snafu::ResultExt;
9
10/// Summary of an import operation.
11#[derive(Debug, Clone)]
12pub struct ImportSummary {
13    pub imported: usize,
14    pub skipped_existing: usize,
15    pub skipped_not_found: usize,
16    pub skipped_invalid: usize,
17}
18
19/// Import annotations from a JSONL reader.
20///
21/// Each line is an `ExportEntry` JSON object. For each entry:
22/// 1. Validate the annotation can be parsed (v1 or v2).
23/// 2. Check if the commit SHA exists locally.
24/// 3. If the commit has no existing note (or `force` is set), write the annotation.
25/// 4. Otherwise skip.
26pub fn import_annotations<R: BufRead>(
27    git_ops: &dyn GitOps,
28    reader: R,
29    force: bool,
30    dry_run: bool,
31) -> Result<ImportSummary> {
32    let mut summary = ImportSummary {
33        imported: 0,
34        skipped_existing: 0,
35        skipped_not_found: 0,
36        skipped_invalid: 0,
37    };
38
39    for line in reader.lines() {
40        let line = line.map_err(|e| crate::error::ChronicleError::Io {
41            source: e,
42            location: snafu::Location::default(),
43        })?;
44
45        let line = line.trim();
46        if line.is_empty() {
47            continue;
48        }
49
50        let entry: ExportEntry = match serde_json::from_str(line) {
51            Ok(e) => e,
52            Err(_) => {
53                summary.skipped_invalid += 1;
54                continue;
55            }
56        };
57
58        // Validate annotation by trying to parse it (handles both v1 and v2)
59        let annotation_json = serde_json::to_string(&entry.annotation).unwrap_or_default();
60        if schema::parse_annotation(&annotation_json).is_err() {
61            summary.skipped_invalid += 1;
62            continue;
63        }
64
65        // Check if commit exists locally
66        let commit_exists = git_ops.commit_info(&entry.commit_sha).is_ok();
67
68        if !commit_exists {
69            summary.skipped_not_found += 1;
70            continue;
71        }
72
73        // Check if note already exists
74        if !force {
75            let has_note = git_ops.note_exists(&entry.commit_sha).context(GitSnafu)?;
76            if has_note {
77                summary.skipped_existing += 1;
78                continue;
79            }
80        }
81
82        if !dry_run {
83            // Write the raw annotation JSON (preserving original format)
84            git_ops
85                .note_write(&entry.commit_sha, &annotation_json)
86                .context(GitSnafu)?;
87        }
88
89        summary.imported += 1;
90    }
91
92    Ok(summary)
93}