1use cadvm_store::ObjectId;
4
5use crate::config;
6use crate::error::Result;
7use crate::format::CadFormat;
8use crate::mesh;
9use crate::model::{CommitBody, FileEntry, Manifest};
10use crate::repo::{Head, Repository};
11use crate::step;
12use crate::worktree;
13
14#[derive(Debug, Clone)]
16pub struct SnapshotOutcome {
17 pub commit_id: ObjectId,
18 pub manifest_id: ObjectId,
19 pub file_count: usize,
20 pub branch: Option<String>,
22}
23
24pub fn build_manifest(repo: &Repository) -> Result<Manifest> {
26 let mut manifest = Manifest::empty();
27 for rel in worktree::scan_step_files(repo)? {
28 let content = worktree::read_working_file(repo, &rel)?;
29 let format = CadFormat::from_path(&rel).expect("scan only yields tracked formats");
30 let blob_ref = repo.store().put_file_content(&content)?;
31 let raw_hash = blob_ref.raw_hash.clone();
32
33 let (line_count, step_metadata, mesh_metadata) = if format.is_brep() {
35 (Some(count_lines(&content)), step::extract(&content), None)
36 } else {
37 (None, None, mesh::extract(&content, format))
38 };
39
40 let entry = FileEntry {
41 path: rel.clone(),
42 format,
43 raw_hash,
44 blob_ref,
45 size_bytes: content.len() as u64,
46 line_count,
47 step_metadata,
48 mesh_metadata,
49 };
50 manifest.files.insert(rel, entry);
51 }
52 Ok(manifest)
53}
54
55pub fn snapshot(repo: &Repository, message: &str, timestamp_unix: i64) -> Result<SnapshotOutcome> {
61 let manifest = build_manifest(repo)?;
62 let file_count = manifest.file_count();
63 let manifest_id = repo.write_manifest(&manifest)?;
64
65 let mut cache = crate::index::HashCache::load(repo);
67 let mut tracked = std::collections::BTreeSet::new();
68 for (rel, entry) in &manifest.files {
69 let _ = cache.record(repo, rel, entry.raw_hash.clone());
70 tracked.insert(rel.clone());
71 }
72 cache.retain(&tracked);
73 let _ = cache.save(repo);
74
75 let parents = match repo.head_commit_id()? {
76 Some(parent) => vec![parent],
77 None => Vec::new(),
78 };
79
80 let body = CommitBody {
81 parents,
82 manifest: manifest_id.clone(),
83 message: message.to_string(),
84 timestamp_unix,
85 author: Some(config::resolve_author(repo)?),
86 };
87 let commit_id = repo.write_commit(&body)?;
88
89 let branch = match repo.read_head()? {
91 Head::Branch(name) => {
92 repo.write_ref(&name, &commit_id)?;
93 Some(name)
94 }
95 Head::Detached(_) => {
96 repo.write_head(&Head::Detached(commit_id.clone()))?;
97 None
98 }
99 };
100
101 Ok(SnapshotOutcome {
102 commit_id,
103 manifest_id,
104 file_count,
105 branch,
106 })
107}
108
109fn count_lines(content: &[u8]) -> u64 {
112 if content.is_empty() {
113 return 0;
114 }
115 let newlines = content.iter().filter(|&&b| b == b'\n').count() as u64;
116 if content.last() == Some(&b'\n') {
117 newlines
118 } else {
119 newlines + 1
120 }
121}