mirror of
https://github.com/rustic-rs/rustic.git
synced 2025-10-26 11:18:51 +00:00
refactor diff command
This commit is contained in:
parent
774604924f
commit
a436fc4522
@ -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 {
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -142,5 +142,5 @@ pub use crate::{
|
||||
SnapshotOptions, StringList,
|
||||
},
|
||||
},
|
||||
repository::{Open, OpenStatus, Repository, RepositoryOptions},
|
||||
repository::{IndexedFull, Open, OpenStatus, Repository, RepositoryOptions},
|
||||
};
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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)
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user