restore: Add glob options to include/exclude patterns

This commit is contained in:
Alexander Weiss 2023-04-09 23:27:06 +02:00
parent 943d067033
commit 9daee094cd
2 changed files with 74 additions and 4 deletions

View File

@ -6,8 +6,11 @@ use std::path::{Component, Path, PathBuf, Prefix};
use std::str;
use anyhow::{anyhow, bail, Result};
use clap::Parser;
use crossbeam_channel::{bounded, unbounded, Receiver, Sender};
use derive_getters::Getters;
use ignore::overrides::{Override, OverrideBuilder};
use ignore::Match;
use indicatif::ProgressBar;
use serde::{Deserialize, Deserializer, Serialize};
@ -103,6 +106,25 @@ impl IntoIterator for Tree {
}
}
#[derive(Default, Clone, Parser)]
pub struct TreeStreamerOptions {
/// Glob pattern to exclude/include (can be specified multiple times)
#[clap(long, help_heading = "EXCLUDE OPTIONS")]
glob: Vec<String>,
/// Same as --glob pattern but ignores the casing of filenames
#[clap(long, value_name = "GLOB", help_heading = "EXCLUDE OPTIONS")]
iglob: Vec<String>,
/// Read glob patterns to exclude/include from this file (can be specified multiple times)
#[clap(long, value_name = "FILE", help_heading = "EXCLUDE OPTIONS")]
glob_file: Vec<String>,
/// Same as --glob-file ignores the casing of filenames in patterns
#[clap(long, value_name = "FILE", help_heading = "EXCLUDE OPTIONS")]
iglob_file: Vec<String>,
}
/// [`NodeStreamer`] recursively streams all nodes of a given tree including all subtrees in-order
pub struct NodeStreamer<BE>
where
@ -112,6 +134,7 @@ where
inner: std::vec::IntoIter<Node>,
path: PathBuf,
be: BE,
overrides: Option<Override>,
}
impl<BE> NodeStreamer<BE>
@ -119,6 +142,10 @@ where
BE: IndexedBackend,
{
pub fn new(be: BE, node: &Node) -> Result<Self> {
Self::new_streamer(be, node, None)
}
fn new_streamer(be: BE, node: &Node, overrides: Option<Override>) -> Result<Self> {
let inner = if node.is_dir() {
Tree::from_backend(&be, node.subtree.unwrap())?
.nodes
@ -131,8 +158,37 @@ where
open_iterators: Vec::new(),
path: PathBuf::new(),
be,
overrides,
})
}
pub fn new_with_glob(be: BE, node: &Node, opts: TreeStreamerOptions) -> Result<Self> {
let mut override_builder = OverrideBuilder::new("/");
for g in opts.glob {
override_builder.add(&g)?;
}
for file in opts.glob_file {
for line in std::fs::read_to_string(file)?.lines() {
override_builder.add(line)?;
}
}
override_builder.case_insensitive(true)?;
for g in opts.iglob {
override_builder.add(&g)?;
}
for file in opts.iglob_file {
for line in std::fs::read_to_string(file)?.lines() {
override_builder.add(line)?;
}
}
let overrides = override_builder.build()?;
Self::new_streamer(be, node, Some(overrides))
}
}
type NodeStreamItem = Result<(PathBuf, Node)>;
@ -149,7 +205,7 @@ where
match self.inner.next() {
Some(node) => {
let path = self.path.join(node.name());
if let Some(id) = node.subtree() {
let is_dir = if let Some(id) = node.subtree() {
self.path.push(node.name());
let be = self.be.clone();
let tree = match Tree::from_backend(&be, *id) {
@ -158,6 +214,15 @@ where
};
let old_inner = mem::replace(&mut self.inner, tree.nodes.into_iter());
self.open_iterators.push(old_inner);
true
} else {
false
};
if let Some(overrides) = &self.overrides {
if let Match::Ignore(_) = overrides.matched(&path, is_dir) {
continue;
}
}
return Some(Ok((path, node)));

View File

@ -15,7 +15,7 @@ use rayon::ThreadPoolBuilder;
use super::rustic_config::RusticConfig;
use super::{bytes, progress_bytes, progress_counter, warm_up_wait};
use crate::backend::{DecryptReadBackend, FileType, LocalDestination};
use crate::blob::{Node, NodeStreamer, NodeType, Tree};
use crate::blob::{Node, NodeStreamer, NodeType, Tree, TreeStreamerOptions};
use crate::commands::helpers::progress_spinner;
use crate::crypto::hash;
use crate::id::Id;
@ -33,6 +33,9 @@ pub(super) struct Opts {
#[clap(long, short = 'n')]
dry_run: bool,
#[clap(flatten)]
streamer_opts: TreeStreamerOptions,
/// Remove all files/dirs in destination which are not contained in snapshot.
/// WARNING: Use with care, maybe first try this first with --dry-run?
#[clap(long)]
@ -284,7 +287,8 @@ fn allocate_and_collect(
.filter_map(Result::ok); // TODO: print out the ignored error
let mut next_dst = dst_iter.next();
let mut node_streamer = NodeStreamer::new(index.clone(), node)?;
let mut node_streamer =
NodeStreamer::new_with_glob(index.clone(), node, opts.streamer_opts.clone())?;
let mut next_node = node_streamer.next().transpose()?;
loop {
@ -410,7 +414,8 @@ fn restore_metadata(
opts: &Opts,
) -> Result<()> {
// walk over tree in repository and compare with tree in dest
let mut node_streamer = NodeStreamer::new(index, node)?;
let mut node_streamer =
NodeStreamer::new_with_glob(index.clone(), node, opts.streamer_opts.clone())?;
let mut dir_stack = Vec::new();
while let Some((path, node)) = node_streamer.next().transpose()? {
match node.node_type() {