Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
156 changes: 82 additions & 74 deletions crates/core/src/archiver/parent.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,11 @@ pub(crate) type ItemWithParent<O> = TreeType<(O, ParentResult<()>), ParentResult
#[derive(Debug)]
pub struct Parent {
/// The tree id of the parent tree.
tree_id: Option<TreeId>,
tree_ids: Vec<TreeId>,
/// The parent tree.
tree: Option<Tree>,
/// The current node index.
node_idx: usize,
trees: Vec<(Tree, usize)>,
/// The stack of parent trees.
stack: Vec<(Option<Tree>, usize)>,
stack: Vec<Vec<(Tree, usize)>>,
/// Ignore ctime when comparing nodes.
ignore_ctime: bool,
/// Ignore inode number when comparing nodes.
Expand Down Expand Up @@ -90,25 +88,28 @@ impl Parent {
pub(crate) fn new(
be: &impl DecryptReadBackend,
index: &impl ReadGlobalIndex,
tree_id: Option<TreeId>,
tree_id: impl IntoIterator<Item = TreeId>,
ignore_ctime: bool,
ignore_inode: bool,
) -> Self {
// if tree_id is given, try to load tree from backend.
let tree = tree_id.and_then(|tree_id| match Tree::from_backend(be, index, tree_id) {
Ok(tree) => Some(tree),
Err(err) => {
warn!(
"ignoring error when loading parent tree {tree_id}: {}",
err.display_log()
);
None
}
});
let (trees, tree_ids) = tree_id
.into_iter()
.filter_map(|tree_id| match Tree::from_backend(be, index, tree_id) {
Ok(tree) => Some(((tree, 0), tree_id)),
Err(err) => {
warn!(
"ignoring error when loading parent tree {tree_id:?}: {}",
err.display_log()
);
None
}
})
.unzip();

Self {
tree_id,
tree,
node_idx: 0,
tree_ids,
trees,
stack: Vec::new(),
ignore_ctime,
ignore_inode,
Expand All @@ -124,27 +125,24 @@ impl Parent {
/// # Returns
///
/// The parent node with the given name, or `None` if the parent node is not found.
fn p_node(&mut self, name: &OsStr) -> Option<&Node> {
match &self.tree {
None => None,
Some(tree) => {
let p_nodes = &tree.nodes;
loop {
match p_nodes.get(self.node_idx) {
None => break None,
Some(p_node) => match p_node.name().as_os_str().cmp(name) {
Ordering::Less => self.node_idx += 1,
Ordering::Equal => {
break Some(p_node);
}
Ordering::Greater => {
break None;
}
},
}
fn p_node(&mut self, name: &OsStr) -> impl Iterator<Item = &Node> {
self.trees.iter_mut().filter_map(|(tree, idx)| {
let p_nodes = &tree.nodes;
loop {
match p_nodes.get(*idx) {
None => break None,
Some(p_node) => match p_node.name().as_os_str().cmp(name) {
Ordering::Less => *idx += 1,
Ordering::Equal => {
break Some(p_node);
}
Ordering::Greater => {
break None;
}
},
}
}
}
})
}

/// Returns whether the given node is the parent of the given tree.
Expand All @@ -166,18 +164,22 @@ impl Parent {
let ignore_ctime = self.ignore_ctime;
let ignore_inode = self.ignore_inode;

self.p_node(name).map_or(ParentResult::NotFound, |p_node| {
if p_node.node_type == node.node_type
&& p_node.meta.size == node.meta.size
&& p_node.meta.mtime == node.meta.mtime
&& (ignore_ctime || p_node.meta.ctime == node.meta.ctime)
&& (ignore_inode || p_node.meta.inode == 0 || p_node.meta.inode == node.meta.inode)
{
ParentResult::Matched(p_node)
} else {
ParentResult::NotMatched
}
})
let mut p_node = self.p_node(name).peekable();
if p_node.peek().is_none() {
return ParentResult::NotFound;
}

p_node
.find(|p_node| {
p_node.node_type == node.node_type
&& p_node.meta.size == node.meta.size
&& p_node.meta.mtime == node.meta.mtime
&& (ignore_ctime || p_node.meta.ctime == node.meta.ctime)
&& (ignore_inode
|| p_node.meta.inode == 0
|| p_node.meta.inode == node.meta.inode)
})
.map_or(ParentResult::NotMatched, ParentResult::Matched)
}

// TODO: add documentation!
Expand All @@ -196,27 +198,35 @@ impl Parent {
index: &impl ReadGlobalIndex,
name: &OsStr,
) {
let tree = self.p_node(name).and_then(|p_node| {
p_node.subtree.map_or_else(
|| {
let mut new_ids: Vec<_> = self
.p_node(name)
.filter_map(|p_node| {
p_node.subtree.or_else(|| {
warn!("ignoring parent node {}: is no tree!", p_node.name);
None
},
|tree_id| match Tree::from_backend(be, index, tree_id) {
Ok(tree) => Some(tree),
Err(err) => {
warn!(
"ignoring error when loading parent tree {tree_id}: {}",
err.display_log()
);
None
}
},
)
});
self.stack.push((self.tree.take(), self.node_idx));
self.tree = tree;
self.node_idx = 0;
})
})
.collect();

// remove potentially identical trees
new_ids.sort();
new_ids.dedup();

let new_tree = new_ids
.into_iter()
.filter_map(|tree_id| match Tree::from_backend(be, index, tree_id) {
Ok(tree) => Some((tree, 0)),
Err(err) => {
warn!(
"ignoring error when loading parent tree {tree_id}: {}",
err.display_log()
);
None
}
})
.collect();
let old_tree = std::mem::replace(&mut self.trees, new_tree);
self.stack.push(old_tree);
}

// TODO: add documentation!
Expand All @@ -225,17 +235,15 @@ impl Parent {
///
/// * If the tree stack is empty.
fn finish_dir(&mut self) -> Result<(), TreeStackEmptyError> {
let (tree, node_idx) = self.stack.pop().ok_or(TreeStackEmptyError)?;

self.tree = tree;
self.node_idx = node_idx;
let tree = self.stack.pop().ok_or(TreeStackEmptyError)?;
self.trees = tree;

Ok(())
}

// TODO: add documentation!
pub(crate) fn tree_id(&self) -> Option<TreeId> {
self.tree_id
self.tree_ids.first().copied()
}

// TODO: add documentation!
Expand Down
110 changes: 54 additions & 56 deletions crates/core/src/commands/backup.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
//! `backup` subcommand
use derive_setters::Setters;
use itertools::Itertools;
use log::info;

use std::path::PathBuf;
Expand Down Expand Up @@ -51,8 +52,8 @@ pub struct ParentOptions {
feature = "clap",
clap(long, value_name = "SNAPSHOT", conflicts_with = "force",)
)]
#[cfg_attr(feature = "merge", merge(strategy = conflate::option::overwrite_none))]
pub parent: Option<String>,
#[cfg_attr(feature = "merge", merge(strategy = conflate::vec::append))]
pub parent: Vec<String>,

/// Skip writing of snapshot if nothing changed w.r.t. the parent snapshot.
#[cfg_attr(feature = "clap", clap(long))]
Expand Down Expand Up @@ -97,30 +98,38 @@ impl ParentOptions {
repo: &Repository<P, S>,
snap: &SnapshotFile,
backup_stdin: bool,
) -> (Option<SnapshotId>, Parent) {
let parent = match (backup_stdin, self.force, &self.parent) {
(true, _, _) | (false, true, _) => None,
(false, false, None) => {
// get suitable snapshot group from snapshot and opts.group_by. This is used to filter snapshots for the parent detection
let group = SnapshotGroup::from_snapshot(snap, self.group_by.unwrap_or_default());
SnapshotFile::latest(
repo.dbe(),
|snap| snap.has_group(&group),
&repo.pb.progress_counter(""),
)
.ok()
}
(false, false, Some(parent)) => SnapshotFile::from_id(repo.dbe(), parent).ok(),
) -> (Vec<SnapshotId>, Parent) {
let parent = if backup_stdin || self.force {
Vec::new()
} else if self.parent.is_empty() {
// get suitable snapshot group from snapshot and opts.group_by. This is used to filter snapshots for the parent detection
let group = SnapshotGroup::from_snapshot(snap, self.group_by.unwrap_or_default());
SnapshotFile::latest(
repo.dbe(),
|snap| snap.has_group(&group),
&repo.pb.progress_counter(""),
)
.ok()
.into_iter()
.collect()
} else {
self.parent
.iter()
.filter_map(|parent| SnapshotFile::from_id(repo.dbe(), parent).ok())
.collect()
};

let (parent_tree, parent_id) = parent.map(|parent| (parent.tree, parent.id)).unzip();
let (parent_trees, parent_ids): (Vec<_>, _) = parent
.into_iter()
.map(|parent| (parent.tree, parent.id))
.unzip();

(
parent_id,
parent_ids,
Parent::new(
repo.dbe(),
repo.index(),
parent_tree,
parent_trees,
self.ignore_ctime,
self.ignore_inode,
),
Expand Down Expand Up @@ -238,44 +247,33 @@ pub(crate) fn backup<P: ProgressBars, S: IndexedIds>(
})
.transpose()?;

match &as_path {
Some(p) => snap
.paths
.set_paths(std::slice::from_ref(p))
.map_err(|err| {
RusticError::with_source(
ErrorKind::Internal,
"Failed to set paths `{paths}` in snapshot.",
err,
)
.attach_context("paths", p.display().to_string())
})?,
None => snap.paths.set_paths(&backup_path).map_err(|err| {
RusticError::with_source(
ErrorKind::Internal,
"Failed to set paths `{paths}` in snapshot.",
err,
)
.attach_context(
"paths",
backup_path
.iter()
.map(|p| p.display().to_string())
.collect::<Vec<_>>()
.join(","),
)
})?,
}
let paths = match &as_path {
Some(p) => std::slice::from_ref(p),
None => &backup_path,
};

let (parent_id, parent) = opts.parent_opts.get_parent(repo, &snap, backup_stdin);
match parent_id {
Some(id) => {
info!("using parent {id}");
snap.parent = Some(id);
}
None => {
info!("using no parent");
}
snap.paths.set_paths(paths).map_err(|err| {
RusticError::with_source(
ErrorKind::Internal,
"Failed to set paths `{paths}` in snapshot.",
err,
)
.attach_context(
"paths",
backup_path
.iter()
.map(|p| p.display().to_string())
.join(","),
)
})?;

let (parent_ids, parent) = opts.parent_opts.get_parent(repo, &snap, backup_stdin);
if parent_ids.is_empty() {
info!("using no parent");
} else {
info!("using parents {}", parent_ids.iter().join(", "));
snap.parent = Some(parent_ids[0]);
snap.parents = parent_ids;
}

let be = DryRunBackend::new(repo.dbe().clone(), opts.dry_run);
Expand Down
Loading
Loading