Skip to content

Commit

Permalink
feat(diff): show diff statistics (#1178)
Browse files Browse the repository at this point in the history
closes #440

### Description

The proposed code contains:
- a DiffStatistics structure used to hold count of stats
- this structure offers a few helping functions that will look at a
given NodeType to increment the associated counter
- it has a Display implementation used to display the statistics in
STDOUT
- this structure is used in the `diff` function

### Exemple

```bash
rustic --log-level debug -r test-repo --password password diff 8e57051d:config ./config
[INFO] using no config file, none of these exist: /home/nardor/.config/rustic/rustic.toml, /etc/rustic/rustic.toml, ./rustic.toml
[INFO] repository local:test-repo: password is correct.
[INFO] using cache at /home/nardor/.cache/rustic/3797e1fe324e97ac068e303e672da3d34af09cd73888bcfd09538cd72692bf7d
[00:00:00] reading index...               ████████████████████████████████████████          2/2                               
[00:00:00] getting snapshot...            ████████████████████████████████████████          0                                
[INFO] getting snapshot...
[00:00:00] getting snapshot...            ████████████████████████████████████████          0                                 
-    "README.md"
M    "bar"
+    "new_dir"
+    "new_dir/new_file"
-    "services"
-    "services/b2.toml"
-    "services/rclone_ovh-hot-cold.toml"
-    "services/s3_aws.toml"
-    "services/s3_idrive.toml"
-    "services/sftp.toml"
-    "services/sftp_hetzner_sbox.toml"
-    "services/webdav_owncloud_nextcloud.toml"
Files:	1 new,	8 removed, 	1 changed
Dirs:	1 new,	1 removed
Others:	0 new,	0 removed
```

### Testing

I am not sure what tests I can add.
Please let me know if you think of any test about this.

Thanks in advance for any feedback.

---------

Co-authored-by: Alexander Weiss <alex@weissfam.de>
Co-authored-by: aawsome <37850842+aawsome@users.noreply.github.com>
  • Loading branch information
3 people committed Aug 10, 2024
1 parent 9bdb7fe commit 1c9969c
Showing 1 changed file with 122 additions and 4 deletions.
126 changes: 122 additions & 4 deletions src/commands/diff.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,10 @@ use crate::{commands::open_repository_indexed, status_err, Application, RUSTIC_A

use abscissa_core::{Command, Runnable, Shutdown};

use std::path::{Path, PathBuf};
use std::{
fmt::Display,
path::{Path, PathBuf},
};

use anyhow::{bail, Context, Result};

Expand Down Expand Up @@ -184,6 +187,102 @@ fn identical_content_local<P, S: IndexedFull>(
Ok(true)
}

/// Statistics about the differences listed with the [`DiffCmd`] command
#[derive(Default)]
struct DiffStatistics {
files_added: usize,
files_removed: usize,
files_changed: usize,
directories_added: usize,
directories_removed: usize,
others_added: usize,
others_removed: usize,
node_type_changed: usize,
metadata_changed: usize,
symlink_added: usize,
symlink_removed: usize,
symlink_changed: usize,
}

impl DiffStatistics {
fn removed_node(&mut self, node_type: &NodeType) {
match node_type {
NodeType::File => self.files_removed += 1,
NodeType::Dir => self.directories_removed += 1,
NodeType::Symlink { .. } => self.symlink_removed += 1,
_ => self.others_removed += 1,
}
}

fn added_node(&mut self, node_type: &NodeType) {
match node_type {
NodeType::File => self.files_added += 1,
NodeType::Dir => self.directories_added += 1,
NodeType::Symlink { .. } => self.symlink_added += 1,
_ => self.others_added += 1,
}
}

fn changed_file(&mut self) {
self.files_changed += 1;
}

fn changed_node_type(&mut self) {
self.node_type_changed += 1;
}

fn changed_metadata(&mut self) {
self.metadata_changed += 1;
}

fn changed_symlink(&mut self) {
self.symlink_changed += 1;
}
}

impl Display for DiffStatistics {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_fmt(format_args!(
"Files :\t{} new,\t{} removed,\t{} changed\n",
self.files_added, self.files_removed, self.files_changed
))?;
// symlink
if self.symlink_added != 0 || self.symlink_removed != 0 || self.symlink_changed != 0 {
f.write_fmt(format_args!(
"Symlinks:\t{} new,\t{} removed,\t{} changed\n",
self.symlink_added, self.symlink_removed, self.symlink_changed
))?;
}
f.write_fmt(format_args!(
"Dirs :\t{} new,\t{} removed\n",
self.directories_added, self.directories_removed
))?;
if self.others_added != 0 || self.others_removed != 0 {
f.write_fmt(format_args!(
"Others :\t{} new,\t{} removed\n",
self.others_added, self.others_removed
))?;
}

// node type
if self.node_type_changed != 0 {
f.write_fmt(format_args!(
"NodeType:\t{} changed\n",
self.node_type_changed
))?;
}

// metadata
if self.metadata_changed != 0 {
f.write_fmt(format_args!(
"Metadata:\t{} changed\n",
self.metadata_changed
))?;
}
Ok(())
}
}

/// Compare two streams of nodes and print the differences
///
/// # Arguments
Expand All @@ -207,40 +306,59 @@ fn diff(
let mut item1 = tree_streamer1.next().transpose()?;
let mut item2 = tree_streamer2.next().transpose()?;

let mut diff_statistics = DiffStatistics::default();

loop {
match (&item1, &item2) {
(None, None) => break,
(Some(i1), None) => {
println!("- {:?}", i1.0);
diff_statistics.removed_node(&i1.1.node_type);
item1 = tree_streamer1.next().transpose()?;
}
(None, Some(i2)) => {
println!("+ {:?}", i2.0);
diff_statistics.added_node(&i2.1.node_type);
item2 = tree_streamer2.next().transpose()?;
}
(Some(i1), Some(i2)) if i1.0 < i2.0 => {
println!("- {:?}", i1.0);
diff_statistics.removed_node(&i1.1.node_type);
item1 = tree_streamer1.next().transpose()?;
}
(Some(i1), Some(i2)) if i1.0 > i2.0 => {
println!("+ {:?}", i2.0);
diff_statistics.added_node(&i2.1.node_type);
item2 = tree_streamer2.next().transpose()?;
}
(Some(i1), Some(i2)) => {
let path = &i1.0;
let node1 = &i1.1;
let node2 = &i2.1;

let are_both_symlink = matches!(&node1.node_type, NodeType::Symlink { .. })
&& matches!(&node2.node_type, NodeType::Symlink { .. });
match &node1.node_type {
tpe if tpe != &node2.node_type => println!("T {path:?}"), // type was changed
// if node1.node_type != node2.node_type, they could be different symlinks,
// for this reason we check:
// that their type is different AND that they are not both symlinks
tpe if tpe != &node2.node_type && !are_both_symlink => {
// type was changed
println!("T {path:?}");
diff_statistics.changed_node_type();
}
NodeType::File if !no_content && !file_identical(path, node1, node2)? => {
println!("M {path:?}");
diff_statistics.changed_file();
}
NodeType::File if metadata && node1.meta != node2.meta => {
println!("U {path:?}");
diff_statistics.changed_metadata();
}
NodeType::Symlink { .. } => {
if node1.node_type.to_link() != node1.node_type.to_link() {
if node1.node_type.to_link() != node2.node_type.to_link() {
println!("U {path:?}");
diff_statistics.changed_symlink();
}
}
_ => {} // no difference to show
Expand All @@ -250,6 +368,6 @@ fn diff(
}
}
}

println!("{diff_statistics}");
Ok(())
}

0 comments on commit 1c9969c

Please sign in to comment.