Skip to main content

cadvm_core/
diff.rs

1//! Manifest-level diffs (added / removed / modified files + metadata deltas).
2//!
3//! This is a *textual / metadata* diff only — it never compares geometry. The
4//! future Open CASCADE stage will add added/removed/common B-Rep diffing.
5
6use std::path::PathBuf;
7
8use cadvm_store::ObjectId;
9use serde::Serialize;
10
11use crate::model::{FileEntry, Manifest};
12
13/// Per-file metadata changes for a modified file.
14#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
15pub struct FileDiff {
16    pub path: PathBuf,
17    pub size_bytes: (u64, u64),
18    pub raw_hash: (ObjectId, ObjectId),
19    pub line_count: (Option<u64>, Option<u64>),
20    pub schema: (Option<String>, Option<String>),
21    pub entity_count: (Option<u64>, Option<u64>),
22    /// Mesh triangle counts (STL/OBJ), when applicable.
23    pub triangles: (Option<u64>, Option<u64>),
24    /// Mesh vertex counts (STL/OBJ), when applicable.
25    pub vertices: (Option<u64>, Option<u64>),
26}
27
28impl FileDiff {
29    fn between(a: &FileEntry, b: &FileEntry) -> Self {
30        let schema = (
31            a.step_metadata.as_ref().and_then(|m| m.file_schema.clone()),
32            b.step_metadata.as_ref().and_then(|m| m.file_schema.clone()),
33        );
34        let entity_count = (
35            a.step_metadata.as_ref().and_then(|m| m.entity_count),
36            b.step_metadata.as_ref().and_then(|m| m.entity_count),
37        );
38        let triangles = (
39            a.mesh_metadata.as_ref().and_then(|m| m.triangles),
40            b.mesh_metadata.as_ref().and_then(|m| m.triangles),
41        );
42        let vertices = (
43            a.mesh_metadata.as_ref().and_then(|m| m.vertices),
44            b.mesh_metadata.as_ref().and_then(|m| m.vertices),
45        );
46        FileDiff {
47            path: a.path.clone(),
48            size_bytes: (a.size_bytes, b.size_bytes),
49            raw_hash: (a.raw_hash.clone(), b.raw_hash.clone()),
50            line_count: (a.line_count, b.line_count),
51            schema,
52            entity_count,
53            triangles,
54            vertices,
55        }
56    }
57}
58
59/// The full diff between two manifests.
60#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize)]
61pub struct ManifestDiff {
62    pub added: Vec<PathBuf>,
63    pub removed: Vec<PathBuf>,
64    pub modified: Vec<FileDiff>,
65}
66
67impl ManifestDiff {
68    /// Whether the two manifests are identical.
69    pub fn is_empty(&self) -> bool {
70        self.added.is_empty() && self.removed.is_empty() && self.modified.is_empty()
71    }
72}
73
74/// Diff manifest `a` (left/old) against manifest `b` (right/new).
75pub fn diff_manifests(a: &Manifest, b: &Manifest) -> ManifestDiff {
76    let mut diff = ManifestDiff::default();
77
78    for (path, entry_b) in &b.files {
79        match a.files.get(path) {
80            None => diff.added.push(path.clone()),
81            Some(entry_a) if entry_a.raw_hash != entry_b.raw_hash => {
82                diff.modified.push(FileDiff::between(entry_a, entry_b));
83            }
84            Some(_) => {}
85        }
86    }
87
88    for path in a.files.keys() {
89        if !b.files.contains_key(path) {
90            diff.removed.push(path.clone());
91        }
92    }
93
94    diff.added.sort();
95    diff.removed.sort();
96    diff.modified.sort_by(|x, y| x.path.cmp(&y.path));
97    diff
98}