refactor diff command

This commit is contained in:
Alexander Weiss 2023-07-21 12:14:09 +02:00
parent 774604924f
commit a436fc4522
6 changed files with 73 additions and 73 deletions

View File

@ -5,7 +5,6 @@ use log::{debug, error, info, trace, warn};
use std::{
cmp::Ordering,
collections::BTreeMap,
io::Read,
num::NonZeroU32,
path::{Path, PathBuf},
sync::Mutex,
@ -18,10 +17,9 @@ use rayon::ThreadPoolBuilder;
use crate::{
error::CommandErrorKind,
hash,
repository::{IndexedFull, IndexedTree},
DecryptReadBackend, FileType, Id, IndexedBackend, LocalDestination, Node, NodeType, Open,
Progress, ProgressBars, ReadBackend, Repository, RusticResult,
BlobType, DecryptReadBackend, FileType, Id, LocalDestination, Node, NodeType, Open, Progress,
ProgressBars, ReadBackend, Repository, RusticResult,
};
pub(crate) mod constants {
@ -167,7 +165,7 @@ impl RestoreOpts {
match (
exists,
restore_infos
.add_file(dest, node, path.clone(), repo.index(), self.verify_existing)
.add_file(dest, node, path.clone(), repo, self.verify_existing)
.map_err(|err| {
CommandErrorKind::ErrorCollecting(path.to_path_buf(), Box::new(err))
})?,
@ -502,12 +500,12 @@ enum AddFileResult {
impl RestoreInfos {
/// Add the file to [`FileInfos`] using `index` to get blob information.
fn add_file(
fn add_file<P, S: IndexedFull>(
&mut self,
dest: &LocalDestination,
file: &Node,
name: PathBuf,
index: &impl IndexedBackend,
repo: &Repository<P, S>,
ignore_mtime: bool,
) -> RusticResult<AddFileResult> {
let mut open_file = dest.get_matching_file(&name, file.meta.size);
@ -543,9 +541,7 @@ impl RestoreInfos {
let mut file_pos = 0;
let mut has_unmatched = false;
for id in file.content.iter().flatten() {
let ie = index
.get_data(id)
.ok_or_else(|| CommandErrorKind::IdNotFound(*id))?;
let ie = repo.get_index_entry(BlobType::Data, id)?;
let bl = BlobLocation {
offset: ie.offset,
length: ie.length,
@ -553,11 +549,9 @@ impl RestoreInfos {
};
let length = bl.data_length();
let matches = open_file.as_mut().map_or(false, |file| {
// Existing file content; check if SHA256 matches
let mut vec = vec![0; length as usize];
file.read_exact(&mut vec).is_ok() && id == &hash(&vec)
});
let matches = open_file
.as_mut()
.map_or(false, |file| id.blob_matches_reader(length as usize, file));
let blob_location = self.r.entry((ie.pack, bl)).or_insert_with(Vec::new);
blob_location.push(FileLocation {

View File

@ -185,8 +185,6 @@ pub enum CommandErrorKind {
ErrorCollecting(PathBuf, Box<RusticError>),
/// error setting length for {0:?}: {1:?}
ErrorSettingLength(PathBuf, Box<RusticError>),
/// did not find id {0} in index
IdNotFound(Id),
/// {0:?}
FromRayonError(#[from] rayon::ThreadPoolBuildError),
/// conversion to `u64` failed: `{0:?}`
@ -274,6 +272,8 @@ pub enum RepositoryErrorKind {
ReadingPasswordFromPromptFailed(std::io::Error),
/// Config file already exists. Aborting.
ConfigFileExists,
/// did not find id {0} in index
IdNotFound(Id),
}
/// [`IndexErrorKind`] describes the errors that can be returned by processing Indizes

View File

@ -1,11 +1,11 @@
use std::{fmt, ops::Deref, path::Path};
use std::{fmt, io::Read, ops::Deref, path::Path};
use binrw::{BinRead, BinWrite};
use derive_more::{Constructor, Display};
use rand::{thread_rng, RngCore};
use serde::{Deserialize, Serialize};
use crate::{error::IdErrorKind, RusticResult};
use crate::{error::IdErrorKind, hash, RusticResult};
pub(super) mod constants {
pub(super) const LEN: usize = 32;
@ -63,6 +63,12 @@ impl Id {
pub fn is_null(&self) -> bool {
self == &Self::default()
}
pub fn blob_matches_reader(&self, length: usize, r: &mut impl Read) -> bool {
// check if SHA256 matches
let mut vec = vec![0; length];
r.read_exact(&mut vec).is_ok() && self == &hash(&vec)
}
}
impl fmt::Debug for Id {

View File

@ -142,5 +142,5 @@ pub use crate::{
SnapshotOptions, StringList,
},
},
repository::{Open, OpenStatus, Repository, RepositoryOptions},
repository::{IndexedFull, Open, OpenStatus, Repository, RepositoryOptions},
};

View File

@ -46,13 +46,14 @@ use crate::{
},
crypto::aespoly1305::Key,
error::{KeyFileErrorKind, RepositoryErrorKind, RusticErrorKind},
index::IndexEntry,
repofile::{
configfile::ConfigFile, keyfile::find_key_in_backend, snapshotfile::SnapshotSummary,
RepoFile,
},
BlobType, Id, IndexBackend, IndexedBackend, LocalDestination, NoProgressBars, Node,
NodeStreamer, PathList, ProgressBars, PruneOpts, PrunePlan, RusticResult, SnapshotFile,
SnapshotGroup, SnapshotGroupCriterion, Tree, TreeStreamerOptions,
NodeStreamer, PathList, ProgressBars, PruneOpts, PrunePlan, ReadIndex, RusticResult,
SnapshotFile, SnapshotGroup, SnapshotGroupCriterion, Tree, TreeStreamerOptions,
};
mod warm_up;
@ -502,6 +503,16 @@ impl<P: ProgressBars, S: Open> Repository<P, S> {
commands::snapshots::get_snapshot_group(self, ids, group_by, filter)
}
pub fn get_snapshot_from_str(
&self,
id: &str,
filter: impl FnMut(&SnapshotFile) -> bool + Send + Sync,
) -> RusticResult<SnapshotFile> {
let p = self.pb.progress_counter("getting snapshot...");
let snap = SnapshotFile::from_str(self.dbe(), id, filter, &p)?;
Ok(snap)
}
pub fn get_snapshots(&self, ids: &[String]) -> RusticResult<Vec<SnapshotFile>> {
let p = self.pb.progress_counter("getting snapshots...");
SnapshotFile::from_ids(self.dbe(), ids, &p)
@ -670,6 +681,16 @@ impl<T, S: Open> Open for IndexedStatus<T, S> {
}
}
impl<P, S: IndexedFull> Repository<P, S> {
pub fn get_index_entry(&self, tpe: BlobType, id: &Id) -> RusticResult<IndexEntry> {
let ie = self
.index()
.get_id(tpe, id)
.ok_or_else(|| RepositoryErrorKind::IdNotFound(*id))?;
Ok(ie)
}
}
impl<P: ProgressBars, S: IndexedTree> Repository<P, S> {
pub fn node_from_snapshot_path(
&self,
@ -684,6 +705,14 @@ impl<P: ProgressBars, S: IndexedTree> Repository<P, S> {
Tree::node_from_path(self.index(), snap.tree, Path::new(path))
}
pub fn node_from_snapshot_and_path(
&self,
snap: &SnapshotFile,
path: &str,
) -> RusticResult<Node> {
Tree::node_from_path(self.index(), snap.tree, Path::new(path))
}
pub fn cat_tree(
&self,
snap: &str,

View File

@ -6,15 +6,14 @@ use crate::{commands::open_repository, status_err, Application, RUSTIC_APP};
use abscissa_core::{Command, Runnable, Shutdown};
use std::io::Read;
use std::path::{Path, PathBuf};
use anyhow::{anyhow, bail, Context, Result};
use anyhow::{bail, Context, Result};
use rustic_core::{
hash, IndexBackend, LocalDestination, LocalSource, LocalSourceFilterOptions,
LocalSourceSaveOptions, Node, NodeStreamer, NodeType, Open, Progress, ProgressBars, ReadIndex,
ReadSourceEntry, RusticResult, SnapshotFile, Tree,
BlobType, IndexedFull, LocalDestination, LocalSource, LocalSourceFilterOptions,
LocalSourceSaveOptions, Node, NodeType, ReadSourceEntry, Repository, RusticResult,
TreeStreamerOptions,
};
/// `diff` subcommand
@ -53,31 +52,25 @@ impl DiffCmd {
fn inner_run(&self) -> Result<()> {
let config = RUSTIC_APP.config();
let repo = open_repository(&config)?;
let repo = open_repository(&config)?.to_indexed()?;
let be = repo.dbe();
let (id1, path1) = arg_to_snap_path(&self.snap1, "");
let (id2, path2) = arg_to_snap_path(&self.snap2, path1);
let progress_options = &config.global.progress_options;
_ = match (id1, id2) {
(Some(id1), Some(id2)) => {
// diff between two snapshots
let p = progress_options.progress_spinner("getting snapshots...");
let snaps = SnapshotFile::from_ids(be, &[id1.to_string(), id2.to_string()], &p)?;
p.finish();
let snaps = repo.get_snapshots(&[id1.to_string(), id2.to_string()])?;
let snap1 = &snaps[0];
let snap2 = &snaps[1];
let index = IndexBackend::new(be, &progress_options.progress_counter(""))?;
let node1 = Tree::node_from_path(&index, snap1.tree, Path::new(path1))?;
let node2 = Tree::node_from_path(&index, snap2.tree, Path::new(path2))?;
let node1 = repo.node_from_snapshot_and_path(snap1, path1)?;
let node2 = repo.node_from_snapshot_and_path(snap2, path2)?;
diff(
NodeStreamer::new(index.clone(), &node1)?,
NodeStreamer::new(index, &node2)?,
repo.ls(&node1, &TreeStreamerOptions::default(), true)?,
repo.ls(&node2, &TreeStreamerOptions::default(), true)?,
self.no_content,
|_path, node1, node2| Ok(node1.content == node2.content),
self.metadata,
@ -85,13 +78,10 @@ impl DiffCmd {
}
(Some(id1), None) => {
// diff between snapshot and local path
let p = progress_options.progress_spinner("getting snapshot...");
let snap1 =
SnapshotFile::from_str(be, id1, |sn| config.snapshot_filter.matches(sn), &p)?;
p.finish();
repo.get_snapshot_from_str(id1, |sn| config.snapshot_filter.matches(sn))?;
let index = IndexBackend::new(be, &progress_options.progress_counter(""))?;
let node1 = Tree::node_from_path(&index, snap1.tree, Path::new(path1))?;
let node1 = repo.node_from_snapshot_and_path(&snap1, path1)?;
let local = LocalDestination::new(path2, false, !node1.is_dir())?;
let path2 = PathBuf::from(path2);
let is_dir = path2
@ -103,24 +93,11 @@ impl DiffCmd {
&self.ignore_opts,
&[&path2],
)?
.map(|item| {
let ReadSourceEntry { path, node, .. } = match item {
Ok(it) => it,
Err(err) => {
status_err!("{}", err);
RUSTIC_APP.shutdown(Shutdown::Crash);
}
};
.map(|item| -> RusticResult<_> {
let ReadSourceEntry { path, node, .. } = item?;
let path = if is_dir {
// remove given path prefix for dirs as local path
match path.strip_prefix(&path2) {
Ok(it) => it,
Err(err) => {
status_err!("{}", err);
RUSTIC_APP.shutdown(Shutdown::Crash);
}
}
.to_path_buf()
path.strip_prefix(&path2).unwrap().to_path_buf()
} else {
// ensure that we really get the filename if local path is a file
path2.file_name().unwrap().into()
@ -129,10 +106,10 @@ impl DiffCmd {
});
diff(
NodeStreamer::new(index.clone(), &node1)?,
repo.ls(&node1, &TreeStreamerOptions::default(), true)?,
src,
self.no_content,
|path, node1, _node2| identical_content_local(&local, &index, path, node1),
|path, node1, _node2| identical_content_local(&local, &repo, path, node1),
self.metadata,
)
}
@ -158,26 +135,20 @@ fn arg_to_snap_path<'a>(arg: &'a str, default_path: &'a str) -> (Option<&'a str>
}
}
fn identical_content_local(
fn identical_content_local<P, S: IndexedFull>(
local: &LocalDestination,
index: &impl ReadIndex,
repo: &Repository<P, S>,
path: &Path,
node: &Node,
) -> Result<bool> {
let Some(mut open_file) = local.get_matching_file(path, node.meta.size) else { return Ok(false) };
for id in node.content.iter().flatten() {
let ie = index
.get_data(id)
.ok_or_else(|| anyhow!("did not find id {} in index", id))?;
let ie = repo.get_index_entry(BlobType::Data, id)?;
let length = ie.data_length();
// check if SHA256 matches
let mut vec = vec![0; length as usize];
if open_file.read_exact(&mut vec).is_ok() && id == &hash(&vec) {
continue;
if !id.blob_matches_reader(length as usize, &mut open_file) {
return Ok(false);
}
return Ok(false);
}
Ok(true)
}