1use anyhow::*;
2use camino::Utf8Path;
3use clap::Parser;
4use tracing::*;
5
6use crate::backend;
7use crate::config::Configuration;
8use crate::diff;
9use crate::fs_tree;
10use crate::hashing::ObjectId;
11use crate::index;
12use crate::ls;
13use crate::snapshot;
14use crate::tree::{self, Forest, Node, NodeType, meta_diff_char};
15
16#[derive(Debug, Parser)]
33#[command(verbatim_doc_comment)]
34#[allow(clippy::doc_lazy_continuation)] pub struct Args {
36 #[clap(short, long)]
38 metadata: bool,
39
40 #[clap(name = "SNAPSHOT_1")]
41 first_snapshot: String,
42
43 #[clap(name = "SNAPSHOT_2")]
44 second_snapshot: Option<String>,
45 }
47
48pub fn run(config: &Configuration, repository: &Utf8Path, args: Args) -> Result<()> {
49 let (_cfg, cached_backend) = backend::open(
50 repository,
51 config.cache_size,
52 backend::CacheBehavior::Normal,
53 )?;
54 let index = index::build_master_index(&cached_backend)?;
55 let blob_map = index::blob_to_pack_map(&index)?;
56 let mut tree_cache = tree::Cache::new(&index, &blob_map, &cached_backend);
57
58 let snapshots = snapshot::load_chronologically(&cached_backend)?;
59 let (snapshot1, id1) = snapshot::find(&snapshots, &args.first_snapshot)?;
60 let snapshot1_forest = tree::forest_from_root(&snapshot1.tree, &mut tree_cache)?;
61
62 let (id2, forest2) = load_snapshot2_or_paths(
63 id1,
64 snapshot1,
65 &snapshot1_forest,
66 &args.second_snapshot,
67 &snapshots,
68 &mut tree_cache,
69 )?;
70
71 diff::compare_trees(
72 (&snapshot1.tree, &snapshot1_forest),
73 (&id2, &forest2),
74 Utf8Path::new(""),
75 &mut PrintDiffs {
76 metadata: args.metadata,
77 },
78 )
79}
80
81fn load_snapshot2_or_paths(
82 id1: &ObjectId,
83 snapshot1: &snapshot::Snapshot,
84 snapshot1_forest: &tree::Forest,
85 second_snapshot: &Option<String>,
86 snapshots: &[(snapshot::Snapshot, ObjectId)],
87 tree_cache: &mut tree::Cache,
88) -> Result<(ObjectId, tree::Forest)> {
89 if let Some(second_snapshot) = second_snapshot {
90 let (snapshot2, id2) = snapshot::find(snapshots, second_snapshot)?;
91 let snapshot2_forest = tree::forest_from_root(&snapshot2.tree, tree_cache)?;
92
93 info!("Comparing snapshot {} to {}", id1, id2);
94
95 Ok((snapshot2.tree, snapshot2_forest))
96 } else {
97 info!(
98 "Comparing snapshot {} to its paths, {:?}",
99 id1, snapshot1.paths
100 );
101 fs_tree::forest_from_fs(
102 tree::Symlink::Read,
107 &snapshot1.paths,
108 Some(&snapshot1.tree),
109 snapshot1_forest,
110 )
111 }
112}
113
114#[derive(Debug, Default)]
115pub struct PrintDiffs {
116 pub metadata: bool,
117}
118
119impl diff::Callbacks for PrintDiffs {
120 fn node_added(&mut self, node_path: &Utf8Path, new_node: &Node, forest: &Forest) -> Result<()> {
121 ls::print_node("+ ", node_path, new_node, ls::Recurse::Yes(forest));
122 Ok(())
123 }
124
125 fn node_removed(
126 &mut self,
127 node_path: &Utf8Path,
128 old_node: &Node,
129 forest: &Forest,
130 ) -> Result<()> {
131 ls::print_node("- ", node_path, old_node, ls::Recurse::Yes(forest));
132 Ok(())
133 }
134
135 fn contents_changed(
136 &mut self,
137 node_path: &Utf8Path,
138 old_node: &Node,
139 new_node: &Node,
140 ) -> Result<()> {
141 assert!(old_node.kind() == NodeType::File || old_node.kind() == NodeType::Symlink);
142 assert_eq!(old_node.kind(), new_node.kind());
143
144 if old_node.kind() == NodeType::Symlink {
145 ls::print_node("- ", node_path, old_node, ls::Recurse::No);
146 ls::print_node("+ ", node_path, new_node, ls::Recurse::No);
147 } else {
148 ls::print_node("C ", node_path, old_node, ls::Recurse::No);
149 }
150 Ok(())
151 }
152
153 fn metadata_changed(
154 &mut self,
155 node_path: &Utf8Path,
156 old_node: &Node,
157 new_node: &Node,
158 ) -> Result<()> {
159 if self.metadata {
160 let leading_char = format!(
161 "{} ",
162 meta_diff_char(&old_node.metadata, &new_node.metadata).unwrap()
163 );
164 ls::print_node(&leading_char, node_path, new_node, ls::Recurse::No);
165 }
166 Ok(())
167 }
168}