Skip to main content

mz_deploy/project/analysis/changeset/
diff.rs

1// Copyright Materialize, Inc. and contributors. All rights reserved.
2//
3// Use of this software is governed by the Business Source License
4// included in the LICENSE file.
5//
6// As of the Change Date specified in that file, in accordance with
7// the Business Source License, use of this software will be governed
8// by the Apache License, Version 2.0.
9
10//! Snapshot diff — finds objects whose hashes changed between two deployments.
11//!
12//! Compares the `objects` maps of two [`DeploymentSnapshot`]s by content hash.
13//! An object is considered **changed** if:
14//!
15//! - It exists in both snapshots but the hashes differ (modified)
16//! - It exists only in the new snapshot (added)
17//! - It exists only in the old snapshot (deleted)
18//!
19//! Because hashes are computed from the normalized typed AST (not raw file
20//! contents), formatting-only changes — whitespace, comment edits, identifier
21//! casing — do **not** produce different hashes and therefore do not appear
22//! in the diff. See [`crate::project::analysis::deployment_snapshot::compute_typed_hash`]
23//! for hash computation details.
24
25use crate::project::analysis::deployment_snapshot::DeploymentSnapshot;
26use crate::project::ir::object_id::ObjectId;
27use crate::verbose;
28use owo_colors::{OwoColorize, Stream, Style};
29use std::collections::BTreeSet;
30
31/// Find changed objects by comparing snapshot hashes.
32pub(super) fn find_changed_objects(
33    old_snapshot: &DeploymentSnapshot,
34    new_snapshot: &DeploymentSnapshot,
35) -> BTreeSet<ObjectId> {
36    let header_style = Style::new().cyan().bold();
37    verbose!(
38        "{} {}",
39        "▶".if_supports_color(Stream::Stderr, |t| t.cyan()),
40        "Comparing deployment snapshots..."
41            .if_supports_color(Stream::Stderr, |t| header_style.style(t))
42    );
43    let mut changed = BTreeSet::new();
44
45    // Objects with different hashes or newly added
46    for (object_id, new_hash) in &new_snapshot.objects {
47        match old_snapshot.objects.get(object_id) {
48            Some(old_hash) if old_hash != new_hash => {
49                verbose!(
50                    "  ├─ {}: {} ({} {} → {})",
51                    "Changed".if_supports_color(Stream::Stderr, |t| t.green()),
52                    object_id
53                        .to_string()
54                        .if_supports_color(Stream::Stderr, |t| t.cyan()),
55                    "hash".if_supports_color(Stream::Stderr, |t| t.dimmed()),
56                    old_hash[..8]
57                        .to_string()
58                        .if_supports_color(Stream::Stderr, |t| t.dimmed()),
59                    new_hash[..8]
60                        .to_string()
61                        .if_supports_color(Stream::Stderr, |t| t.dimmed())
62                );
63                changed.insert(object_id.clone());
64            }
65            None => {
66                verbose!(
67                    "  ├─ {}: {} ({} {})",
68                    "New".if_supports_color(Stream::Stderr, |t| t.green()),
69                    object_id
70                        .to_string()
71                        .if_supports_color(Stream::Stderr, |t| t.cyan()),
72                    "hash".if_supports_color(Stream::Stderr, |t| t.dimmed()),
73                    new_hash[..8]
74                        .to_string()
75                        .if_supports_color(Stream::Stderr, |t| t.dimmed())
76                );
77                changed.insert(object_id.clone());
78            }
79            _ => {}
80        }
81    }
82
83    // Deleted objects
84    for object_id in old_snapshot.objects.keys() {
85        if !new_snapshot.objects.contains_key(object_id) {
86            verbose!(
87                "  ├─ {}: {}",
88                "Deleted".if_supports_color(Stream::Stderr, |t| t.red()),
89                object_id
90                    .to_string()
91                    .if_supports_color(Stream::Stderr, |t| t.cyan())
92            );
93            changed.insert(object_id.clone());
94        }
95    }
96
97    verbose!(
98        "  └─ Found {} changed object(s)",
99        changed
100            .len()
101            .to_string()
102            .if_supports_color(Stream::Stderr, |t| t.bold())
103    );
104    changed
105}