1use std::io::Write;
2
3use crate::error::chronicle_error::GitSnafu;
4use crate::error::Result;
5use crate::git::GitOps;
6use crate::schema::annotation::Annotation;
7use serde::{Deserialize, Serialize};
8use snafu::ResultExt;
9
10#[derive(Debug, Clone, Serialize, Deserialize)]
12pub struct ExportEntry {
13 pub commit_sha: String,
14 pub timestamp: String,
15 pub annotation: Annotation,
16}
17
18pub fn export_annotations<W: Write>(git_ops: &dyn GitOps, writer: &mut W) -> Result<usize> {
23 let note_list = list_annotated_commits(git_ops)?;
24 let mut count = 0;
25
26 for sha in ¬e_list {
27 let note_content = match git_ops.note_read(sha).context(GitSnafu)? {
28 Some(content) => content,
29 None => continue,
30 };
31
32 let annotation: Annotation = match serde_json::from_str(¬e_content) {
33 Ok(a) => a,
34 Err(_) => continue, };
36
37 let entry = ExportEntry {
38 commit_sha: sha.clone(),
39 timestamp: annotation.timestamp.clone(),
40 annotation,
41 };
42
43 let line =
44 serde_json::to_string(&entry).map_err(|e| crate::error::ChronicleError::Json {
45 source: e,
46 location: snafu::Location::default(),
47 })?;
48
49 writeln!(writer, "{line}").map_err(|e| crate::error::ChronicleError::Io {
50 source: e,
51 location: snafu::Location::default(),
52 })?;
53
54 count += 1;
55 }
56
57 Ok(count)
58}
59
60fn list_annotated_commits(_git_ops: &dyn GitOps) -> Result<Vec<String>> {
62 let output = std::process::Command::new("git")
66 .args(["notes", "--ref", "refs/notes/chronicle", "list"])
67 .output()
68 .map_err(|e| crate::error::ChronicleError::Io {
69 source: e,
70 location: snafu::Location::default(),
71 })?;
72
73 if !output.status.success() {
74 return Ok(Vec::new());
76 }
77
78 let stdout = String::from_utf8_lossy(&output.stdout);
79 let shas: Vec<String> = stdout
80 .lines()
81 .filter_map(|line| {
82 let parts: Vec<&str> = line.split_whitespace().collect();
84 if parts.len() >= 2 {
85 Some(parts[1].to_string())
86 } else {
87 None
88 }
89 })
90 .collect();
91
92 Ok(shas)
93}